42TUI
Refresh the Intra project graph
The project_data.json used by the local TypeScript graph tools is served by
the Projects website, which uses a browser session. An API OAuth bearer
token does not create that website session. To refresh the graph automatically:
- Install dependencies with
npm install, then install the browser withnpx playwright install chromium. On a machine with a compatible Chromium already installed, setBROWSER_EXECUTABLEinstead. - Copy
.env.exampleto.envand setINTRA_LOGIN,INTRA_PASSWORD,CURSUS_ID, andCAMPUS_ID. Keep.envprivate; it is gitignored. - Run
bun fetch-graph-auto.ts. The first login checks Remember me if the 42 login page offers it. Later runs reuse the saved browser profile in.local/intra-graph-browser/. If the session expires, the script logs in again with the credentials in.env.
The output is graph.<cursus>.<campus>.json, which is gitignored. A failed
login or non-JSON response leaves the previous graph file in place. For a
scheduled server refresh, run the same command from the repository root with
a private .env; do not run overlapping jobs against the same browser profile.
If 42 asks for MFA or presents a challenge, the unattended login stops and
needs a fresh interactive authentication. HEADLESS=false opens a browser
for diagnosing a changed login flow.
42TUI is an offline-first Python TUI for managing projects, work time, and notes for École 42 students. Local project data remains in SQLite. Optional 42 account connection uses a centralized Bun API with PostgreSQL, Redis, browser-based OAuth, and device-bound sessions; the local app does not receive the user's 42 password or OAuth client secret. See the OAuth backend operations runbook to deploy that service.
Getting started
Production installation
Once the installer service and first PyPI release are live, install on Linux/macOS:
curl -fsSL https://tui.42wip.in/install.sh | sh
The installer uses uv, obtains Python >=3.12 when needed (including on Ubuntu
22.04 with system Python 3.10), and installs 42tui in an isolated user environment.
The installer configures PATH for Bash/Zsh without duplicate exports; new terminals
find 42tui automatically. Use the printed executable path to launch immediately
in the current terminal. Optional Nerd Font installation
is available with --font; you must select the font in your terminal settings.
An outdated system uv is handled by installing a compatible copy in your user
directory, without sudo. Account connection opens the default browser, falls back to installed Chrome/Chromium,
and displays a copyable login link you can open on another device;
installing Firefox through Snap is not required.
Windows support is experimental; a PowerShell installer, exact-version installs, upgrades, uninstall and migration from pipx are described in the production deployment guide.
Manage a uv/pipx installation without opening the interface:
42tui update
42tui uninstall
# Equivalent: 42tui remove
For production uv installations, a standalone uninstaller is also available:
curl -fsSL https://tui.42wip.in/uninstall.sh | sh
Uninstall preserves local data, account credentials, fonts, shell PATH settings, and shared Python/uv installations. Source/editable checkouts use their original manager for maintenance.
Development from this checkout
Python 3.12 or newer is required (including the textual-image dependency).
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
42tui
For editable pipx installation, use the repository's AGENTS.md instructions
with an explicitly compatible Python interpreter.
The database is created under $XDG_DATA_HOME/42tui/42tui.sqlite3 (or
~/.local/share/42tui/42tui.sqlite3). Existing projects remain available locally without an
account connection. Connect a 42 account in Settings to synchronize real projects from the
centralized API. Choosing Save + Set Up Checkout can clone the development repository URL
entered for a project. Uninstalling the application preserves this local data.
Project browsing
The catalogue uses official project IDs while SQLite keeps its own IDs for notes, timers and checkout folders. Shared API metadata, the cursus/campus web graph and account progression are stored separately. Choose the cursus and campus in Settings. Common Core views display old + shared or new + shared exclusively. When the available evidence cannot select a track, choose one in Settings. Empty quests do not establish the old track or rank zero. Track preferences are scoped to the account, cursus and campus.
My 42 projects shows account projects in the selected track and the known current
and next Common Core ranks. Rank resolution is automatic and uses quest evidence
first. When rank quests are absent, an active, unvalidated rank-0 project can infer
rank 0 from personal progress (source: project). This requires fresh personal
sources and a fresh graph for the selected cursus/campus, a resolved track, and
no validated rank-0 project, higher-rank attempt, specialization attempt or missing
classification. Empty history alone never implies rank 0. Saved manual ranks are
ignored. Project ranks are inferred from graph rules, not official API levels.
All projects shows every Common Core project in the selected track until a
quest confirms Common Core completion, then shows specialization projects. An
unknown personal rank still permits browsing the full Common Core catalogue.
Registerable projects
uses only the account's registration API response, including ID-only projects whose
catalogue description is unavailable. Specializations groups branches within the
selected track. Active and local projects keeps local work accessible when the
track is unknown. Search, type filters and pagination apply to the selected view.
In My 42 projects, choosing a specific type browses all matching projects in
the selected track, including later ranks and specializations, even when your
personal rank is unknown. Returning to All types restores the current/next
rank view. Other views keep their section, registration or local-work limits.
The browser starts in My 42 projects. Loaded projects are sorted by ascending
rank, with exams first within each rank, then by name.
Unranked projects appear last. Exams have a dedicated badge and accent border.
Click a rank header or press Enter on it to collapse or expand the group.
Group state is kept during the session
for each account, cursus/campus and view.
Rank headers show completed/total counts for loaded projects matching the filters.
Headers and project names use status colors: green for completed, accent for
active, amber for partial progress or available, red for failed, and muted for
inactive. Project status unknown is displayed as INACTIVE.
Details show exams, web difficulty as XP (including zero), nullable duration,
classification provenance, structured requirements, recommendations and raw
unparsed rules. Drawing links are visual relationships, not prerequisites.
Project details show Solo or Group when matching session metadata is available,
including in cached views. Known group sizes appear as Group · 3 members;
missing or conflicting session counts remain unspecified. Register submits directly through the API; 42 validates
eligibility even when team mode is unknown. API registration requires the projects
scope and fresh project history and catalog data. Registration refreshes status and
prevents duplicate submissions; creating or joining a team is a separate step.
The Intra project link remains available for the complete requirements.
The backend publishes validated API/browser snapshots through a separate ingestion worker. TUI reads never start a browser. Catalogue and branch caches are scoped by cursus, campus and snapshot; personal observations are isolated by account. Offline views show saved data with its collection time and availability. Registration is fresh for five minutes, progression/quests for fifteen minutes and catalogue for 24 hours. An unavailable source preserves its prior observation as stale.
The TUI downloads all catalogue pages for both tracks and the unclassified section of the selected cursus/campus into the existing SQLite database. Reopening the app reuses that complete local copy for 24 hours; searches and project filters run locally. Project details are saved on first selection and reused for the same snapshot, including offline. Account progress still refreshes separately at startup. Expired catalogues remain visible while a replacement downloads; failed or interrupted downloads preserve the previous complete snapshot. No database reset is needed for this cache.
The last known dashboard profile and goals are also saved locally. After a successful sync, reopening with the same device credentials restores the selected cursus, track, rank, Journey and account details before network refreshes complete. The profile snapshot is bound to the backend and credential fingerprints; tokens and private keys are never stored in this cache. Saved goals are marked stale while refreshing, and pending or failed observations preserve the last known display.
To check the cache against synthetic HTTP responses, run
postman simulation run postman/simulations/catalog-cache.sim.yaml --port 4901
and, in another terminal,
CATALOG_CACHE_SIMULATION_URL=http://127.0.0.1:4901/backend PYTHONPATH=src .venv/bin/python -m unittest tests.test_catalog_cache_simulation.
The new SQLite schema refuses older databases with an explicit reset instruction. Back up local notes and activity before intentionally deleting an old database; the application never resets it automatically.
Project testers
In Projects, use the Testers button beside the project name or press t
to reveal and focus the tester controls. The Testers section offers Install Francinette, then
Run Francinette, for libft, ft_printf and get_next_line (including their
42cursus- slugs). One image serves all three projects. Installation needs no
project folder; running needs an existing local folder configured with e.
Born2beroot has no bundled tester yet.
42TUI fetches a pinned commit of
WaRtr0/francinette-image and builds
its Dockerfile, without running its installer or editing shell configuration.
Docker uses linux/amd64 or linux/arm64 on Linux and macOS with x86_64
(Intel/AMD) or ARM64 processors. Francinette prefers the Docker engine's native
architecture, avoiding x86_64 emulation on ARM64. This also respects the CPU of
a remote Docker engine. 32-bit x86 is unsupported.
Docker Engine, Docker Desktop and other compatible local engines use the standard
Docker CLI. The active context and DOCKER_* variables are respected and kept
for the entire operation, including cleanup. A Linux engine is required.
Before running, 42TUI checks the selected container architecture and reads/writes a temporary probe
inside the project folder to verify local file sharing and permissions.
On Linux, a regular daemon runs testers with your UID/GID; rootless Docker retains
its mapped container root. Francinette uses a private writable copy of its code
for logs and caches, so generated project files remain owned by your account.
During each run, private Clang wrappers default debug information to DWARF 4,
compatible with the upstream Ubuntu 22.04 image's Valgrind. Memory checks remain
enabled; compiler arguments and explicit debug-format options are preserved.
The wrappers and writable tester copy are removed when the run finishes.
The upstream Dockerfile fetches dependencies at build time; pinning the wrapper
repository does not freeze those dependencies. The temporary container mounts the
project read/write at /project and runs Francinette from its writable copy,
keeping /project as the working directory.
Tester output appears in the terminal; press any key to return to 42TUI. Ctrl+C
interrupts the operation and removes its temporary container. An exit status of
zero means the tester finished; inspect its output for individual test results.
Logs are saved under $XDG_DATA_HOME/42tui/testers/logs, or
~/.local/share/42tui/testers/logs. Failed operations offer Retry.
The small × button beside a tester removes its Docker images (all installed versions and CPU architectures) and stopped 42TUI test/probe containers. It also works without a configured project folder. Since the installation is shared, removing Francinette affects all three projects; use Install Francinette to restore it. Running containers, project files, other testers and Docker volumes are left in place. Docker can retain shared layers and build cache, so removing the images does not guarantee reclaiming their entire displayed size. See Docker's resource cleanup documentation.
42TUI reuses working Docker installations. Otherwise it distinguishes a missing
CLI, a stopped engine and denied access. Check Docker access again retries
detection after setup. On macOS, Install Docker offers Homebrew when available,
or downloads and opens the official installer for the host processor. Finish
Docker Desktop setup
yourself and start Docker before rechecking. On supported Ubuntu and Debian
releases, the confirmation dialog lists the commands for Docker's official apt
repository before any administrator command runs. Conflicting system packages
must be resolved manually; 42TUI does not remove them. Other distributions link to
the official installation instructions. For access problems, follow the
Linux post-install steps,
then sign out and back in; never run the entire application with sudo.
The bundled ft_tui/testers.toml is loaded at startup and extended by
$XDG_CONFIG_HOME/42tui/testers.toml, falling back to ~/.config/42tui/testers.toml.
Each user entry replaces the entire entry with the same id; multiple entries
may target a project. Disable a bundled tester with:
[[testers]]
id = "francinette"
enabled = false
To add a tester, copy this template and replace the repository, full commit SHA,
Docker build context and command with real values. The command is an argument
array executed inside the container, without shell expansion. Repository URLs must
use HTTPS, context paths must remain inside the repository, and platforms are
linux and/or darwin:
[[testers]]
id = "my-tester"
name = "My tester"
projects = ["libft", "42cursus-ft_printf"]
platforms = ["linux", "darwin"]
repository = "https://github.com/your-account/your-tester.git"
ref = "0123456789abcdef0123456789abcdef01234567"
context = "."
command = ["/tester/run", "--verbose"]
Restart 42TUI to reload changes. Invalid user TOML or entries display an error and
leave the complete bundled catalog usable. Image identities include the tester,
commit, repository and context; availability is always checked against Docker.
ARM64 images have a separate tag, so existing amd64 images are preserved.
On ARM64, install the native image once from the tester controls after upgrading.
Optional architectures = ["amd64", "arm64"] allows native execution for custom
testers that support both CPUs. Existing custom entries default to ["amd64"];
a tester restricted to one CPU still requires emulation on other CPUs. When the
engine's CPU is unavailable, the first configured architecture is used.
See the verification report for tested environments
and remaining platform checks.
Project checkout setup
In Projects, select a project and press e. Choose its local folder and optional
development remote, then select Save + Set Up Checkout. 42TUI clones the development remote
into a missing or empty folder, or initializes an empty local Git repository when no development
remote is set. Existing repositories are preserved; non-empty non-Git folders and conflicting
remote URLs are rejected.
When the checkout has commits, setup ensures local dev and master branches. A cloned checkout
switches to dev, associated with origin/dev. master is reserved for Vogsphere, whose accepted
submission branch is master; once the Intra API supplies its URL, 42TUI configures
vogsphere/master. No branch is pushed automatically. Add a real development URL yourself if you
want setup to clone it. For a new empty local repository, Git cannot store a
master branch before the first commit; run setup again after creating that commit to create
master.
In project details, Start timer records a manual work session in SQLite. A running timer
survives closing and reopening 42TUI; starting a timer for another project stops the previous
one. Subject checks run at the interval selected in Settings (six hours by default). 42TUI looks
for subject.pdf at the project root, calculates its SHA-256, and asks the configured provider
for a reference fingerprint. The backend queues subject work for the unified Intra worker,
which uses the same authenticated browser profile as Graph and Pace. It discovers the subject
link on the project page, prefers English, validates the PDF, and caches it with its SHA-256
in Redis. The HTTP handler never opens a browser or calls the restricted attachments endpoint.
The TUI shows SUBJECT · QUEUED while waiting and checks for the result every 30 seconds.
Missing or different PDFs are atomically installed as subject.pdf; failed downloads preserve
the existing file. Configured folders work with or without a Git repository. Worker snapshots
are fresh for six hours and retained for 24 hours. PDF contents are not extracted.
Project setup adds *.pdf to .gitignore so a downloaded untracked
subject isn't accidentally committed; PDFs already tracked by Git remain tracked until removed manually.
Use Open subject to launch the PDF with the OS default application or TDF, selected in Settings.
TDF must be installed separately and available on PATH.
Keyboard shortcuts
j/k: move down or up through projectsh/l: switch to the previous or next tabe: set up or edit the selected project's local path and development remoteHome/End: select the first or last projectCtrl+D/Ctrl+U: move down or up one pageF1–F5: help, projects, notes, Git Sync, and settingsq: quit
In Settings, choose a favorite editor (Neovim, Vim, Visual Studio Code, or Zed) and a favorite
Textual theme. From Projects, select a configured project and press o or use Open in editor
to open its folder. Terminal editors temporarily take over the terminal; GUI editors open separately.
In Settings → Appearance, image rendering defaults to Automatic: 42TUI uses
the Sixel or Kitty graphics backend selected by textual-image, with colored Unicode
blocks as a fallback. You can force either protocol, select colored blocks, or turn
images off. Forced protocols require support in the terminal and any multiplexer
between it and 42TUI. Rendering is selected by protocol support, not CPU/GPU hardware.
Icons have independent Automatic, Nerd Font, Simple Unicode, and Text only
modes, with a preview. Automatic mode uses terminal/environment hints, not a scan of
installed fonts: those do not establish which font the terminal uses, especially over
SSH. NERDFONT=1 or NERDFONT=0 overrides the automatic estimate. An explicit icon
choice in Settings takes precedence. These preferences are saved locally and apply
without restarting; font shape and pixel size remain controlled by the terminal.
42goto shell navigation
42goto changes the current shell directory to a project's configured local folder. Enable the
shell function and project completion in your shell startup file:
# Bash: add to ~/.bashrc
eval "$(command 42goto --shell-init bash)"
# Zsh: add to ~/.zshrc
eval "$(command 42goto --shell-init zsh)"
Then run 42goto to jump to the currently active project, or 42goto <project> to open a project
by its name or slug. Press Tab to complete configured project slugs. A fresh editor heartbeat
defines the active project; when there is no fresh heartbeat, the most recently tracked project
is used. If there is no activity history, a single configured in-progress project is used; otherwise
42goto asks you to specify the project. Project folders are read from the same local SQLite database
as 42TUI. After updating a source checkout, reinstall the package to refresh the CLI entry points:
pip install -e .
Editor activity tracking
42TUI accepts local activity heartbeats from editor plugins, even when the TUI is closed. Configure the project folder in 42TUI first, then have an editor integration call the shared CLI command when the active file changes, is edited, or is saved:
42tui heartbeat --file /absolute/path/to/project/src/main.c --editor nvim --quiet
42tui heartbeat --file /absolute/path/to/project/src/main.c --editor vscode --write --quiet
The CLI finds the deepest configured project folder containing the file. It stores the project,
editor, event time, and save flag in SQLite; it does not store the file path or read file contents.
Elapsed time is attributed between recent heartbeats, with a 120-second idle cutoff and a shared
timeline across editors to prevent double counting. Files outside configured project folders are
ignored with an error. Editor-specific plugins can call this command, so each integration only
needs to report its active file and editor ID; installing the app itself requires no network access.
See integrations/ for the current VS Code, Zed, Vim, and Neovim adapters.
Roadmap
Implemented for the local MVP:
- Local project catalog, dashboard, and SQLite persistence; centralized API project sync after OAuth connection
- Profile settings, default projects folder, and per-project local path / development remote setup
- Project navigation, Git hygiene, manual timer, PDF fingerprinting, and Markdown dev log
- Local editor heartbeat ingestion and project-level coding time aggregation
- Dashboard activity streaks, four-week heatmap, peak coding day/hour, and rolling validation pace against Black Hole deadlines
- Editor integrations for VS Code, Zed, Vim, and Neovim
- First-run onboarding for profile, projects folder, editor, theme, and language
- English, French, and Spanish interface with JSON catalogs and English fallback
Available with the optional OAuth backend:
- Browser-based 42 account pairing and profile/project synchronization, while preserving local SQLite data
Planning is maintained in ROADMAP.md, grouped by target version.
TODO.md indexes all features and unscheduled ideas; each feature has
its explanation and checklist in todos/.
Source layout
Run the service regression tests from the project root after installing the package:
python -m unittest discover -s tests -v
src/ft_tui/
├── api/ # centralized API client and provider contract
├── config/ # local paths and settings
├── db/ # models and SQLite access
├── services/ # business logic independent from the UI
├── ui/ # Textual application and views
├── locales/ # English-fallback JSON catalogs (French and Spanish)
└── main.py # CLI entry point
To add another interface language, add src/ft_tui/locales/<code>.json with
English source messages as keys, then register its code and display name in
ft_tui/i18n.py. Any untranslated message falls back to English.
Project attempts and evaluations
For a connected account, select a project and open Attempts & evaluations from Project Details. The dedicated screen selects the current team and lets you browse previous attempts, members, scores, repository and lock/closure dates. Expand peer corrections to read comments and available feedback; expand automated evaluations to read their output. Scheduled, overdue/unfilled and completed corrections have separate labels. Missing marks remain distinct from zero.
Use arrow keys to select an attempt, Tab/Enter to expand an evaluation, r to
refresh, i to open Intra, and Escape to return to the selected project. The
layout stacks on terminals narrower than 90 columns. Activity, evaluation details,
automated output and embedded team results are cached by account for up to 30 days. Reopening the screen reuses these observations;
press Refresh to fetch changes from Intra. Cached sources show their collection
time separately; unavailable fields and hidden participants stay explicit.
Team management, booking and feedback submission are tracked in TODO.md.
Both the updated backend and TUI are required. Live 42 response parity remains unverified. See the HTTP simulation instructions for local integration verification and the sandbox limitation.
Friends and rankings
Connect your 42 account and select a cursus in Settings, then open Friends. Add friends by their 42 login to see their level, ongoing projects and recent cluster location. Open a friend for project history or “Compare with me” to find shared projects and opportunities to help each other. Rankings compares cursus working curriculum ranks among your friends and yourself, with equal places for tied ranks. Published old/new cursus project mappings determine the estimate; level remains visible.
Search and filters cover shared projects, campus membership and active cluster sessions. Enter opens a profile; Escape returns to the list. Narrow terminals show one pane at a time. Saved progression remains available offline, while cluster presence always requires a recent successful lookup. Refresh runs in the background while the tab is open; progression caches refresh every 15 minutes and presence every minute. The updated backend must be deployed before using online Friends. Campus rankings and historical growth remain follow-up work in TODO.md.
Search in Friends and Projects accepts abbreviations and small typos, ignores
case, accents and separators, and supports multiple words in any order. For
example, pswp or puhs swap finds push_swap. Friends search also matches
display names, campuses and ongoing projects; rankings retain their original
positions when filtered. Project search behaves consistently offline and in
catalog results. Deploy the updated backend to enable fuzzy catalog queries.
The Add friend field searches a shared student directory stored in the backend. Type at least two characters, press Down to browse matches and Enter to choose a login, then select Add friend. It matches names, abbreviations and small typos, excludes existing friends and your account, and works across campuses. Results show when the index is building or stale. Selecting Add verifies the numeric user ID and handles renamed logins. Exact-login entry remains available. Deploy the new migration and run the directory worker to populate and refresh the index; typing never scans the 42 API.
Clubs
Open Clubs to create an invite-only campus group, add registered 42TUI students, and recruit players with Babyfoot, Smash or custom templates. Members join or waitlist; organizers start ready activities manually. Club managers and configured campus representatives can publish announcements.
The global DND button (Ctrl+Shift+D) suspends Clubs notifications. Each club
has its own bell; added members and the campus group start muted. Invitations
appear at the top right without taking focus (Ctrl+Shift+N focuses them).
Activities and announcements remain browsable while muted. Spontaneous popups
require fresh campus presence on an unlocked workstation.
Apply the Clubs database migration and restart both backend and TUI before use. See the MVP guide for deployment, current limitations and verification, and the feature plan for later phases.
Metadata
Release files for ft-tui 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| ft_tui-0.3.0.tar.gz | 305.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ft_tui-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 663.0 kB
Release files / ft_tui-0.3.0.tar.gz
| Download URL | ft_tui-0.3.0.tar.gz |
|---|---|
| Size | 305.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
61bc7056e26376c5df33fb9bbceaf6ef043a404e9c706eb7f1edb643b37c7d18
|
|
BLAKE2b-256 checksum How to use checksums |
f8eae862bd3b8305694696b56d9500a7ad784954839905284d8912a0a46e4426
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 26, 2026.
Transparency logRelease files / ft_tui-0.3.0-py3-none-any.whl
| Download URL | ft_tui-0.3.0-py3-none-any.whl |
|---|---|
| Size | 357.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
425d8609095bb750a5974e422767c71b191391ec4d6db8bc12ea78f496e7ba39
|
|
BLAKE2b-256 checksum How to use checksums |
e11e98e08cf45976341e859d4c929125a96676b10e0e44d02b498b79dd7a8650
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 26, 2026.
Transparency logSecurity for ft-tui 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for ft_tui-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 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.