Skip to main content

plyngent

Single-user LLM chat and agent toolkit for the terminal.

Python 3.14+. OpenAI-compatible APIs (including DeepSeek OpenAI-compat), OpenAI Responses with optional hosted tools, SQLite session memory, workspace-scoped file/process/VCS tools, and a readline REPL with slash commands.

Requires Python 3.14+ on your PATH (or via uv / pipx).

Install

Quick try (uvx)

No permanent install — runs the published package in a temporary environment:

uvx plyngent --help
uvx plyngent chat

User tool install

Keep plyngent on your PATH as a managed tool:

# uv (recommended)
uv tool install plyngent
plyngent --help

# pipx
pipx install plyngent
plyngent --help

Upgrade later:

uv tool upgrade plyngent
# or: pipx upgrade plyngent

pip (venv or user)

python3.14 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -U pip
pip install plyngent
plyngent --help

Or user install (if you accept that layout):

pip install --user plyngent

From a git clone (development)

pdm install          # first time
pdm sync             # after pull
pdm run plyngent --help

Dev checks (same order as CI):

pdm run ruff check .
pdm run ruff format --check .   # or: pdm run ruff format .  to apply
pdm run basedpyright .
pdm run pytest

Commit gateway (prek): runs ruff check + format and basedpyright on git commit so format is not forgotten.

uv tool install prek    # once
prek install            # once per clone (installs .git/hooks/pre-commit)
prek run --all-files    # run all hooks on demand

Config: prek.toml. CI still runs the same checks in GitHub Actions.

Basic usage

# 1) Create / open config
plyngent config path
plyngent config edit    # $VISUAL/$EDITOR, else system open (xdg-open/open/startfile)

# Minimal provider (OpenAI platform — Responses API; preset defaults to openai):
# [providers.oai]
# access_key_or_token = "sk-..."
# # models default: gpt-5.4, gpt-5.4-mini, gpt-5.4-nano
# # provider_tools default: web_search  (use provider_tools = [] to disable)

# 2) Chat
plyngent chat
plyngent chat --provider oai --model gpt-5.4-mini
plyngent chat -p "Summarize this repo" --provider oai --model gpt-5.4-mini --no-stream

# 3) List providers from config
plyngent providers

# 4) Plugins (entry-point allowlist under [plugins])
plyngent plugins list
plyngent plugins enable acme

In the REPL: type normally, use /help for slash commands, """ … """ for multiline, /markdown for Rich rendering, /quit to leave.

Configure

Default config path (platformdirs):

plyngent config path
plyngent config edit    # $VISUAL/$EDITOR (e.g. codium --wait), else system default

Copy the example and fill in a real token:

cp doc/plyngent.example.toml "$(plyngent config path)"
# then edit providers

Minimal shape:

[providers.local]
preset = "openai-compatible"
url = "https://api.openai.com/v1"
access_key_or_token = "sk-..."
# Optional HTTP timeouts (seconds). Default: connect=10, read=600.
# timeout = 120
# timeout = { connect = 10, read = 600 }

[providers.local.models]
"gpt-4o-mini" = { text = true }

[agent]
# system_prompt = persona (omit → built-in). tool_directives = tool playbook.
# system_prompt = "" / tool_directives = "" disable each part; both "" → no system.
# Or multi-line override (prefer '''...'''):
# system_prompt = '''Your custom persona...'''
# tool_directives = '''### Workspace ...'''
confirm_destructive = true
max_context_tokens = 200000
# Fold MCP server initialize ``instructions`` (usage guidance) into the
# system prompt when tools are on (default true; false ignores server text).
# mcp_instructions = true
# Static out-of-workspace pre-allow (path → read|write|exec); never prompts.
# allow_paths = { "/data/datasets" = "read", "/tmp/build" = "exec" }
# Auto-raise tool/PTY limits without prompting (same as answering yyy).
# auto_continue_limits = false
# Thinking strength: "" (provider default) | none | minimal | low | medium |
# high | xhigh | max. See "Thinking strength" below; providers and single
# models may override either value.
# reasoning_effort = "medium"
# thinking_budget_tokens = 8000   # Anthropic only; overrides the level's budget

# Optional skills (instruction directories). See doc/skills.md.
# [skills]
# enabled = true                  # false = no skill tools
# paths = ["~/team-skills"]       # explicit roots, highest priority
# discover = ["plyngent", "claude"]  # our user dir + other harnesses' dirs
# include_project_roots = true    # also <workspace>/.claude/skills
# allow_write = false             # true = skip the per-write grant prompt

# Optional plugins (entry-point names); default load none. See doc/plugins.md.
# [plugins]
# enable = ["acme"]
# Notifications while a prompt waits on you. See doc/notifications.md.
# [notify]
# grace_seconds = 5.0             # announce only after this long
# channels = ["auto"]             # auto | desktop | terminal
# actions = true                  # allow / deny / review buttons + reply field
# include_reason = false          # off: the reason may name a command or URL

# Deletes: `delete_path` moves the path to the system trash. See doc/trash.md.
# [trash]
# enabled = true                  # false = a delete is final again

Per-provider timeout is passed to the HTTP session for chat/completions, Responses, and GET /models. A single number sets one timeout; { connect, read } splits TCP/TLS setup vs idle wait between response bytes (SSE can run longer than read while chunks keep arriving). Tool/process timeouts (run_argv, PTY, policy confirm) are separate.

Thinking strength (reasoning_effort) can be set on [agent], on a provider, or on a single model: the most specific layer wins, per field, so a model may override only the effort or only the budget. "" sends nothing and lets the provider apply its own default. Each API surface maps the value onto its own wire field — chat completions send reasoning_effort, Responses send reasoning.effort, and Anthropic has no effort level at all, so it sends thinking with the token budget that level maps onto (minimal 1024 … xhigh/max 32768; none disables thinking); a thinking block the model refuses is retried once with the shape it takes — adaptive thinking with the level itself as output_config.effort (from Claude 4.7 on), or no block at all where thinking cannot be turned off — and the refusal is remembered for the session, so thinking_budget_tokens is ignored on such a model. max is DeepSeek's own top level on chat completions and clamps to xhigh everywhere else. thinking_budget_tokens overrides that mapping; either way the request stays valid: the budget never drops below 1024, and when it would swallow the output limit max_tokens grows so the answer still has room. DeepSeek chat keeps thinking.type = "enabled" unless the effort is none.

[providers.deepseek]
reasoning_effort = "high"          # provider default

[providers.deepseek.models]
"deepseek-v4-pro" = { text = true, reasoning_effort = "max" }  # per-model wins

[providers.anthropic]
reasoning_effort = "medium"
thinking_budget_tokens = 16000     # explicit Anthropic thinking budget

Third-party plugins: install a package that declares project.entry-points."plyngent.tools" (and later other groups), then allowlist the entry-point name under [plugins].enable. Details: doc/plugins.md.

Skills (instruction directories with a SKILL.md): discovered under [skills].paths, our own user skills directory, and other harnesses' skill directories (~/.claude/skills, plus <workspace>/.claude/skills when include_project_roots). A session-start notice (a host notice, never a system-prompt edit) tells the model that the skill tools exist and how to use them — skill_list to see what exists, skill_read for a SKILL.md, skill_search for content — and deliberately lists nothing; skill_create / skill_edit write one behind a timed once/session/no grant ([skills].allow_write = true for a standing one; the path denylist still wins and --yes//yolo never skips it). Reading needs no grant: skill roots are read-only roots, so read_file / regex_files see them too while writes stay denied. Details: doc/skills.md.

Notifications while a prompt waits: a turn stops at a confirm, a directory-access request or an ask_user_* question, so if you walked away you only find out on your return. After [notify].grace_seconds (5s) the prompt is announced — with the answers that prompt accepts as buttons where the platform renders them (a confirmation: Allow once / Deny / Go to terminal; a limit prompt: Continue / Continue, stop asking / Stop here; a skill write: Allow once / This chat / Deny) and a reply field for a question — and the answer is taken as if typed at the terminal (never as a session grant). The desktop channel needs the optional extra (pip install 'plyngent[notify]'); without it the CLI writes an OSC 777 / OSC 9 escape sequence to /dev/tty, which every modern terminal turns into a native notification with no dependency at all. Answering at the terminal inside the grace period stays silent, and a prompt that follows an answer given on the notification is announced at once; [notify].include_reason keeps the reason line (a command, argv or URL) out by default; the notification is kept on screen (no expiry) until the prompt is answered instead of collapsing into the tray; and no channel can break a prompt: a missing extra or a refusing desktop falls through to the next one. Details: doc/notifications.md.

Deletes go to the trash: delete_path moves the path into the desktop's own trash — the freedesktop trash on Linux, the Finder trash on macOS, the Recycle Bin on Windows — so a delete the agent made on its own can be undone from the file manager you already use. That matters because the tool is YOLO-tagged: under --yes / /yolo on it runs without a confirmation. Ask for the old behaviour with permanent=true, or turn it off for every call with [trash] enabled = false. Paths under the system temp directory are scratch and are deleted outright, a trashing that fails (a volume with no usable trash directory, a broken install) leaves the file alone and says so instead of deleting it, and a shell rm never uses the trash. Details: doc/trash.md.

MCP servers (Model Context Protocol over stdio): define servers under [mcp.servers.<name>] (command + args, optional env/cwd/timeout/read_only) and their tools become agent tools namespaced mcp__<server>__<tool> (LOCAL tags; read_only = true also marks them READ_ONLY, eligible for /btw --tools=read). Names in [mcp].disable stay disconnected. A server may also return optional instructions (usage guidance) in its MCP initialize response; when tools are on those are folded into the agent system prompt as MCP server <name> instructions: blocks ([agent] mcp_instructions = false disables), and /mcp previews them. In the REPL, /mcp lists per-server status and tool counts; /mcp reconnect re-reads the config file and restarts every enabled server (adopts newly added ones).

Supported provider presets today:

Preset API used by the agent Notes
openai (default if preset omitted) OpenAI Responses (POST /responses) Default models gpt-5.4 / gpt-5.4-mini / gpt-5.4-nano when models is omitted; optional provider_tools (default web_search)
openai-compatible Chat Completions Generic hosts (vLLM, LiteLLM, proxies); requires url
deepseek Chat Completions (DeepSeek) Default models deepseek-flash / deepseek-v4-pro when models is omitted; set convention = "responses" (provider or per-model) to use the OpenAI Responses API (POST /responses, currently only deepseek-flash), or convention = "anthropic" to use the Anthropic Messages API on https://api.deepseek.com/anthropic
anthropic Anthropic Messages (POST /messages) Native tools/streaming; set access_key_or_token (API key)

Model-level preset / url overrides on a catalog entry can route a single provider name to a different API (e.g. gateway + Anthropic model). DeepSeek models may also carry a per-model convention (empty = inherit the provider-level one), e.g. serve deepseek-flash over Responses while deepseek-v4-pro stays Chat Completions:

[providers.deepseek]
preset = "deepseek"
access_key_or_token = "sk-..."

[providers.deepseek.models]
"deepseek-flash" = { text = true, convention = "responses" }
"deepseek-v4-pro" = { text = true }

DeepSeek's Anthropic convention (convention = "anthropic") points at https://api.deepseek.com/anthropic by default and is fully supported by the agent's Anthropic Messages path (tools, streaming, usage). DeepSeek maps claude-opus* model ids to deepseek-v4-pro and claude-haiku* / claude-sonnet* (and unknown ids) to deepseek-flash server-side; real DeepSeek ids pass through as-is. GET /models is not documented on that base, so model selection is config-driven.

On the Chat Completions and Responses conventions DeepSeek ignores a hosted search tool, so plyngent offers its own web_search tool instead. It queries DeepSeek's Search API (POST /v1/tools/search, one request per search, billed per successful request, 5 QPS account-wide) and can fall back to Bing's RSS (search_sources = ["deepseek", "bing"]) when that fails or finds nothing. search_sources sets the source order ([] disables the tool); provider_tools = [] on the Anthropic convention switches that surface to the same local tool.

If [database] is omitted (or SQLite url is unset/empty), chat uses a durable file under the user data dir (e.g. ~/.local/share/plyngent/chat.db on Linux). Set url = ":memory:" for a true in-memory SQLite (CLI warns; no file; useful for tests).

Chat

Interactive REPL

plyngent chat
plyngent chat --provider local --model gpt-4o-mini
plyngent chat --workspace /path/to/project --new
plyngent chat --session 3
Flag Meaning
--provider / --model Select from config (required when multiple and non-interactive)
--workspace Tool root (default: cwd); sessions bind to this path
--new / --session ID Fresh session vs resume by id
--tools / --no-tools Default tools on
--max-rounds Tool-loop rounds per turn (default 32)
--stream / --no-stream Streaming deltas (default on)
--quiet Less status on stderr
--yes YOLO on: skip destructive-tool confirms for this process
--auto-continue Auto-raise tool/PTY limits without prompting (like answering yyy)
--log-level On the root CLI: DEBUG, INFO, WARNING, …

Sessions resume the most recently updated session for the current workspace unless you pass --new or --session. Each session remembers the last provider and model (restored on resume so you are not re-prompted). A session resumed by a fresh plyngent process also gets a [notice] resume: … line: the model is told that process-scoped state — PTY sessions, running commands, temporary workspaces — did not survive the restart, and that an unfinished turn may have been cut off. The notice is kept with the transcript; --quiet hides only the user-facing line.

When a tool-loop or PTY limit is hit, the prompt accepts y (continue once), n (stop), or yyy — stop asking for the rest of this turn (the next user turn prompts again). --auto-continue / [agent] auto_continue_limits = true skip the prompt for every turn.

Tool calls show progress as they run: every builtin tool prints a * Read 'src/x.py' prefix the moment the call starts, and the outcome (L1-80 (done), (exit code 0), (session 3)) is appended once the result lands; MCP tools render as * MCP <server>:<tool> (done). Plugin tools keep the [tool] … / [tool ok] … style. Piped output and --verbose print the finished line as before.

One-shot (scripts / CI)

plyngent chat -p "Summarize README.md" --provider local --model gpt-4o-mini --no-stream
echo "hello" | plyngent chat --provider local --model gpt-4o-mini

Exit codes (one-shot):

Code Meaning
0 Success
1 Config / usage error
2 Cancelled
3 Turn failed (API / incomplete)

Input ergonomics

  • Multiline: start a message with """, end a later line with """.
  • /edit: compose a turn in $VISUAL/$EDITOR (blocking only; empty cancels).
  • Tab: completes slash commands and some arguments (provider, model, on/off, export, /help targets).

Slash commands

Type /help in the REPL for the live list. Common ones:

Command Purpose
/status Provider, session, context/usage estimates
/history [N | last [N]] Conversation grouped by turn: default = the last turn (your message + the model's answer); N = turn N, last N = the last N turns
/history -v / -vv The turn's ends (your message, the answer) always print in full; -v adds every intermediate row (tool calls/results, notices) as a one-liner, -vv prints those in full too, and --preview collapses every row. --message N prints one message in full. Numbers are display ordinals shown by /history, never database rows (they restart with the session)
/sessions Sessions for this workspace
/new /resume /rename /delete Session lifecycle (/delete confirms)
/export [md|json] [path] Transcript from DB (no secrets)
/compact Soft-compact + model summary into a new session
/stream /verbose /markdown /tools /rounds Toggles and limits
/yolo [on|off|once] Soft destructive confirms: sticky skip, off, or next turn only

| /retry | Retry/continue the incomplete last user turn (after error, cancel, or output limit) | | /btw [--tools read\|no\|full] [--fresh] | Side question without changing the main session (read-only tools by default; the model is told the exchange is unsaved and how its tools are scoped) | | /provider /model | Switch without restarting | | /model --persist | Save current model id into plyngent.toml catalog | | /models | List config + remote GET /models (always re-fetches) | | /models --persist | Merge remote catalog into TOML for this provider | | /todos | Todo/task stack: list (items numbered as listed), push, pop, done, clear | | /skills [list\|search\|open] | List discovered skills, regex-search them, or open one's directory in the system file manager | | /grants | Directory-access grants: list, or revoke <index\|all> | | /config | Edit plyngent.toml ($VISUAL/$EDITOR or system open); reload after blocking editor | | /quit | Leave the REPL |

User messages are saved immediately. On API error or Ctrl+C, unfinished assistant text is discarded — tool calls that already produced a result stay, so /retry does not run them again — but the user message stays so /retry works after resume. Interactive auto-retry waits 5s, 10s, 15s, 20s, then +10s each step (10 attempts); an attempt that gets a full model round back resets that budget, so a connection that recovers and drops again starts over from 5s. A round cut off at the output limit (max_tokens) is not an error: what the model had written — thinking blocks included — stays in the transcript, its tool calls are dropped (they are re-issued, not replayed), and a notice tells it where it stopped, so up to 2 further rounds carry on from there; when that budget is spent the turn ends with the partial answer kept and /retry continues it. Tool calls of one round run in parallel, but writes to the same file apply one at a time, in call order — a later edit sees the earlier one's result instead of stale text.

Ctrl+C cancels the in-flight turn, and during the auto-retry countdown it cancels the retry. Model-initiated prompts run off the event loop: a wait prompt is cancelled by Ctrl+C (the tool reports cancelled by user and the turn continues), while ask_user_* and confirm prompts ignore it with a one-line hint — answer them to continue. Ctrl+C never exits the REPL (use Ctrl+D or /quit), and MCP servers run in their own process group, so it never kills them either.

Workspace model

  • Workspace = root for file/process/VCS tools (default cwd).
  • Session = SQLite chat bound to a workspace path.
  • Paths outside the workspace need a directory-access grant: the model calls request_directory_access (read / write / exec), the human approves at the requested level or another one (timed y/N, default deny; --yes//yolo auto-approve as a process-only grant), and approval lasts for the session (/grants lists/revokes). Static pre-allow lives under [agent].allow_paths. The path denylist always wins, and grants gate path-resolving tools only — they are not a sandbox (run_argv/PTY reach the whole filesystem).
  • Resuming a session from another directory prompts: keep session workspace, rebind to current, or abort.

Tools (when enabled)

Default registry: file ops (including tree with a markdown bullet-list default, flat paths or classic decorated on demand; default noise-dir skips), run_argv / run_argv_batch / PTY (POSIX openpty; Windows ConPTY via pywinpty), read-only VCS (git), HTTP fetch_local (GET/POST/PUT/DELETE via niquests; private/loopback hosts need a human policy grant, not YOLO) plus fetch on DeepSeek providers (public pages as Markdown through the provider's Fetch API), human prompts (ask_user_line / ask_user_choice / ask_user_form), wait (line prompt with timeout; Enter disturbs), request_directory_access (out-of-workspace access grants), get_truncated (resume any truncated result via its truncate_token=...), skills (skill_list / skill_read / skill_search / skill_create / skill_edit), and todo stack tools (todo_list / todo_push / todo_pop / todo_update / todo_clear).

Skills are instruction directories shared with other harnesses: a folder with a SKILL.md (YAML frontmatter — name, description, … — plus a Markdown body) and optional bundled files (scripts, references). Discovery, roots, grants, and the authoring format are documented in doc/skills.md; the short version is that a session-start notice points the model at them (skill_list / skill_read / skill_search) without listing anything, so what exists (and the bodies) is read on demand rather than injected, and writing a skill is a separate grant (a timed once/session/no prompt, or [skills].allow_write) that is not a directory-access grant and is never skipped by YOLO.

Safety defaults:

  • Paths stay under the workspace unless granted (request_directory_access / [agent].allow_paths); the refusal names the mode that would cover the call; optional path_denylist substrings always apply (tree also skips denylisted children by default).

  • Command basename denylist (e.g. dangerous shells/utilities).

  • Destructive tools (delete/move/overwrite) can require confirm (confirm_destructive; default deny in non-TTY). Override for the session with /yolo on|off|once or startup --yes (path/command denylists still apply).

  • write_file refuses to replace an existing file unless overwrite=true; the confirm asks about that replacement.

  • PTY sessions: caps, idle TTL, output budget; master FD is non-inheritable; sessions closed on chat exit. Prefer file tools over full-screen editors (vim/nano) for edits. read_pty sanitizes CSI/controls so tool results cannot reprogram the host TTY (no host terminal reset on exit). write_pty is literal text only; use write_pty_keys for keys and bytes: <c-c> <esc> <enter> <s-tab> <up> … <f12>, <c-up> <s-home> <c-s-f5> for modifier combinations, <up*3> to repeat, <m-x> for Alt, and \xHH / \uHHHH for raw bytes. An unknown <…> is an error, never text typed into the terminal. For passwords/sudo/ssh prompts use ask_into_pty (human types locally; answer never returns to the model).

  • Mis-typed tool arguments are answered, never guessed: a string where an array is expected (run_argv argv, ask_user_choice options, ask_user_form fields), a call that omits a required argument, or a non-numeric wait duration (numeric strings are accepted) returns an error showing the shape/names to send — the human never sees a garbage prompt, and no confirm prompt fires for a call that cannot run.

Usage / context (CLI)

  • Context size prefers API prompt_tokens from the last model call; otherwise a char-based estimate (~4 chars/token).
  • Turn/session usage sum billed completion usage across tool rounds (history is re-sent each round).
  • Soft compact can calibrate from reported prompt_tokens. See /status.

Other commands

plyngent providers          # list configured providers
plyngent config path|edit
plyngent --log-level INFO chat ...

Architecture (short)

See doc/architecture.md and AGENTS.md for developers.

  • lmproto/ — OpenAI-compatible, OpenAI Responses, Anthropic Messages, DeepSeek msgspec models and async SSE clients
  • agent/ — kind-based tool loop (chat_completions / responses / messages), streaming, usage, compact
  • memory/ — async SQLAlchemy sessions/messages
  • tools/ — workspace tools
  • cli/ — Click entry + slash registry (awaitlet bridges sync Click to async work)
  • Multi-tenant web (web/) is not in scope for the single-user CLI (Phase H).

License

MIT

Metadata

Release files for plyngent 0.13.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for plyngent 0.13.0
File Size Uploaded Provenance
plyngent-0.13.0.tar.gz 488.4 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for plyngent 0.13.0
File Interpreter ABI Platform
plyngent-0.13.0-py3-none-any.whl Python 3 none any Details

Total release size: 864.9 kB

Release files / plyngent-0.13.0.tar.gz

Download URL plyngent-0.13.0.tar.gz
Size 488.4 kB
Tags Source
SHA-256 checksum
How to use checksums
ddc93d7d0d7d7cc0bfd3496c84cc186b6e11653a6984d3ef4e0d7e6c2db15ba1
BLAKE2b-256 checksum
How to use checksums
06676df2ab4f75c003f5244428825a405ee51e0d36e2a04384c8251dd6c57a5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Release files / plyngent-0.13.0-py3-none-any.whl

Download URL plyngent-0.13.0-py3-none-any.whl
Size 376.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
58f5ad3ec706b884c2369667a1806f6c1f050f53f9765a995a18c0b768e7f387
BLAKE2b-256 checksum
How to use checksums
75be38b8fb93754fb0da87c166e9b6883b76c1319545365cbd44496faaef00c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Security for plyngent 0.13.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.

Select from 2 files

Attestation for plyngent-0.13.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 23, 2026.

Transparency log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page