mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-21 16:21:42 +02:00
Add Codex plugin manifest, stdlib MCP server, SHA-pinned Actions
- .codex-plugin/plugin.json and assets/icon.svg package the existing skill tree for Codex plugin catalogs; skill version 1.0.12 across all manifests - `remove-ai-watermarks mcp` serves identify, remove and batch over stdio, one CLI command per tool call, standard library only; server.json and glama.json for the MCP registry and Glama - Pin every GitHub Action to a commit SHA - Call module.train(False) instead of module.eval(), which plugin scanners read as dynamic code execution Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
db006f034f
commit
06c0cb1bbe
41 files changed
+1288
-76
No files matched your search
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Agent skill and Claude Code plugin for the remove-ai-watermarks CLI.",
|
||||
"version": "1.0.11"
|
||||
"version": "1.0.12"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "remove-ai-watermarks",
|
||||
"version": "1.0.12",
|
||||
"description": "Identify and remove AI provenance marks from images and video the user generated or edited, using the remove-ai-watermarks CLI.",
|
||||
"author": {
|
||||
"name": "wiltodelta",
|
||||
"url": "https://github.com/wiltodelta"
|
||||
},
|
||||
"homepage": "https://raiw.cc",
|
||||
"repository": "https://github.com/wiltodelta/remove-ai-watermarks",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"ai-watermark",
|
||||
"watermark-removal",
|
||||
"c2pa",
|
||||
"synthid",
|
||||
"provenance",
|
||||
"gemini",
|
||||
"sora"
|
||||
],
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "Remove AI Watermarks",
|
||||
"shortDescription": "Identify and remove AI watermarks and provenance metadata",
|
||||
"longDescription": "Runs the open-source remove-ai-watermarks CLI locally to identify C2PA, IPTC, EXIF and TC260 provenance, visible AI-generator labels and supported invisible watermarks, then removes them from images and video the user generated or edited. It does not remove stock, agency or other third-party marks.",
|
||||
"developerName": "wiltodelta",
|
||||
"category": "Productivity",
|
||||
"capabilities": ["Read", "Write"],
|
||||
"websiteURL": "https://github.com/wiltodelta/remove-ai-watermarks",
|
||||
"defaultPrompt": [
|
||||
"Check this image for AI watermarks and provenance metadata",
|
||||
"Remove the AI metadata from my generated video without re-encoding it",
|
||||
"Remove the visible AI label from the corner of my image"
|
||||
],
|
||||
"brandColor": "#A78BFA",
|
||||
"composerIcon": "./assets/icon.svg",
|
||||
"logo": "./assets/icon.svg"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# Local state and secrets never belong in a plugin bundle.
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.oracle-slots.json
|
||||
.local-eval/
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
# Generated media and model artifacts.
|
||||
data/
|
||||
*.mp4
|
||||
*.onnx
|
||||
*.safetensors
|
||||
*.pt
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
PYTHON
|
||||
|
||||
- name: Upload distributed release identity
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: distributed-release-${{ github.run_attempt }}
|
||||
path: release-identity.json
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the tap
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
repository: wiltodelta/homebrew-tap
|
||||
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||
@@ -227,7 +227,7 @@ jobs:
|
||||
needs: resolve
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Publish the agent skill when its version moved
|
||||
env:
|
||||
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
||||
|
||||
@@ -21,8 +21,8 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Download derivative weights
|
||||
|
||||
@@ -36,9 +36,9 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
|
||||
- name: Verify release tag matches package version
|
||||
run: |
|
||||
|
||||
@@ -32,8 +32,8 @@ jobs:
|
||||
skill:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
- name: Sync dev environment
|
||||
run: uv sync --frozen --extra dev
|
||||
- name: Agent skill packaging tests
|
||||
|
||||
+10
-10
@@ -16,8 +16,8 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
- name: Sync dev environment
|
||||
run: uv sync --frozen --extra dev
|
||||
- name: Check C2PA soft-binding registry snapshot
|
||||
@@ -34,8 +34,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
with:
|
||||
enable-cache: false
|
||||
# Policy and triaged blocks: docs/development.md "Known security-gate blocks".
|
||||
@@ -69,8 +69,8 @@ jobs:
|
||||
- os: windows-latest
|
||||
python-version: "3.14"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Sync dev environment
|
||||
@@ -82,8 +82,8 @@ jobs:
|
||||
name: versioned transport contracts py3.14
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
with:
|
||||
python-version: "3.14"
|
||||
- name: Create clean runtime contract environment
|
||||
@@ -104,8 +104,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install ffmpeg
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
steps:
|
||||
- name: Download distributed release identity
|
||||
if: ${{ github.event_name == 'workflow_run' }}
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
pattern: distributed-release-*
|
||||
path: distribution-identities
|
||||
|
||||
@@ -40,6 +40,8 @@ Before a release, read [`docs/release-and-distribution.md`](docs/release-and-dis
|
||||
|
||||
The published Agent Skill is [`skills/remove-ai-watermarks/`](skills/remove-ai-watermarks/). Update it in the same change as any CLI edit that alters commands, extras, exit codes, mark keys, or the intended-use boundary. Install and publish steps: [`docs/agent-skill.md`](docs/agent-skill.md).
|
||||
|
||||
The same skill tree is the Codex plugin (`.codex-plugin/plugin.json`). `remove-ai-watermarks mcp` is a standard-library MCP server that runs one CLI command per tool call; keep it dependency-free, because the MCP registry launches it as a bare `uvx remove-ai-watermarks mcp` that cannot add extras ([`docs/mcp-server.md`](docs/mcp-server.md)).
|
||||
|
||||
## Module architecture
|
||||
|
||||
[`docs/module-internals.md`](docs/module-internals.md) is the canonical per-module map, including design decisions, thresholds, calibration history, incident records, and regression guards. Read the relevant section before changing a subsystem.
|
||||
|
||||
@@ -482,9 +482,23 @@ Claude Code can add this repository as a plugin marketplace:
|
||||
/plugin install remove-ai-watermarks@remove-ai-watermarks
|
||||
```
|
||||
|
||||
Codex reads the same skill through [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json).
|
||||
The skill refuses stock-agency and other third-party paid-asset marks. See
|
||||
[the agent skill guide](docs/agent-skill.md).
|
||||
|
||||
## MCP server
|
||||
|
||||
`remove-ai-watermarks mcp` serves `identify`, `remove` and `batch` as Model
|
||||
Context Protocol tools over stdio, each a thin wrapper around one CLI command:
|
||||
|
||||
```json
|
||||
{"mcpServers": {"remove-ai-watermarks": {"command": "uvx", "args": ["remove-ai-watermarks", "mcp"]}}}
|
||||
```
|
||||
|
||||
See [the MCP server guide](docs/mcp-server.md).
|
||||
|
||||
<!-- mcp-name: io.github.wiltodelta/remove-ai-watermarks -->
|
||||
|
||||
## ComfyUI
|
||||
|
||||
The separate
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="512" height="512" fill="none" stroke="url(#lg)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!--
|
||||
The raiw.cc brand mark: the Lucide "sparkle" and "x" glyphs.
|
||||
Lucide, https://lucide.dev, ISC License, Copyright (c) Lucide Contributors.
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
-->
|
||||
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z" fill="url(#lg)" fill-opacity="0.15"/>
|
||||
<g transform="translate(1 17) scale(0.3)" stroke-width="5">
|
||||
<path d="M18 6 6 18"/>
|
||||
<path d="m6 6 12 12"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="lg" x1="0" y1="0" x2="24" y2="24">
|
||||
<stop stop-color="#a78bfa"/>
|
||||
<stop offset="1" stop-color="#06b6d4"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
+13
-2
@@ -70,6 +70,17 @@ Do these in order after the skill is on the default branch.
|
||||
are indexed via clawskills.sh, and the ClawHub prerequisite in step 6 is
|
||||
now met. The list still requires proven real-world usage. Re-attempt once
|
||||
the ClawHub listing shows organic installs.
|
||||
- `hashgraph-online/awesome-codex-plugins`: READY, not submitted. It lists
|
||||
only Codex plugins, so it needs `.codex-plugin/plugin.json` and the icon in
|
||||
`assets/icon.svg`. Its contribution CI runs `scripts/validate-plugin-pr.py`
|
||||
against the repository's `HEAD.zip`, then the HOL plugin scanner with a
|
||||
minimum score of 80 and no high finding; that scan job is
|
||||
`continue-on-error`, so it reports rather than blocks. The scanner's `\beval\s*\(`
|
||||
rule also matches PyTorch's `.eval()`, which is why the code calls
|
||||
`.train(False)`. On 2026-10-17 `plugin-scanner` 3.49.0 scored the tree 86
|
||||
with no high finding. The remaining loss is a tracked video over the
|
||||
scanner's 16 MiB file budget. Recheck with
|
||||
`uvx --from plugin-scanner==3.49.0 plugin-scanner scan . --min-score 80 --fail-on-severity high`.
|
||||
5. **Claude community marketplace.** Form at
|
||||
[platform.claude.com/plugins/submit](https://platform.claude.com/plugins/submit)
|
||||
(individuals) or the claude.ai directory form (Team/Enterprise). Validate
|
||||
@@ -191,8 +202,8 @@ that names a command, flag or value the previous release lacked. Left behind, it
|
||||
CLI is current right before it types an option that build rejects.
|
||||
|
||||
Bump `metadata.version` in `SKILL.md` plus `version` in
|
||||
`skills/.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json` when
|
||||
the skill instructions change. That version is independent of the PyPI package
|
||||
`skills/.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` and
|
||||
`.codex-plugin/plugin.json` when the skill instructions change. That version is independent of the PyPI package
|
||||
version.
|
||||
|
||||
## Behavior evals
|
||||
|
||||
+11
@@ -28,6 +28,7 @@ defaults. This page focuses on choosing the right command.
|
||||
| `video invisible` and `video all --invisible` | `remove-ai-watermarks[video,diffusion]` plus ffmpeg on PATH |
|
||||
| HEIC/HEIF/AVIF pixel input | Add `remove-ai-watermarks[heif]` |
|
||||
| Every production command and Python-compatible backend | `remove-ai-watermarks[all]` |
|
||||
| `mcp` | Default package; each tool needs the extra of the command it runs |
|
||||
|
||||
`batch` requires the same extra as its selected mode. Extras can be combined in
|
||||
one installation, for example `remove-ai-watermarks[visible,detect,heif]`.
|
||||
@@ -690,6 +691,16 @@ For `visible` and `all` modes, the final summary includes counts for
|
||||
validation results are printed explicitly, but the once-filled outputs are kept
|
||||
and the batch does not retry them automatically.
|
||||
|
||||
## Serve the CLI to agent clients
|
||||
|
||||
```bash
|
||||
remove-ai-watermarks mcp
|
||||
```
|
||||
|
||||
Runs a Model Context Protocol server on stdin and stdout whose `identify`,
|
||||
`remove` and `batch` tools each run one command of this CLI. Client setup and
|
||||
the tool contract are in the [MCP server guide](mcp-server.md).
|
||||
|
||||
## Exit behavior
|
||||
|
||||
The CLI uses nonzero exit codes for meaningful incomplete outcomes, including
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@ to run the tool. Use the maintainer references only when changing the code.
|
||||
| [Code provenance](code-provenance.md) | Required notices for licensed derivative work. |
|
||||
| [Verification plan](verification-plan.md) | Verification methods, completed measurements, and remaining validation gaps. |
|
||||
| [Release and distribution](release-and-distribution.md) | PyPI, Homebrew, Hugging Face Space, photo-classify Hub model, ComfyUI Registry, and the release workflow. |
|
||||
| [Agent skill](agent-skill.md) | Install and maintain the published Agent Skill and Claude plugin. |
|
||||
| [Agent skill](agent-skill.md) | Install and maintain the published Agent Skill, the Claude plugin and the Codex plugin. |
|
||||
| [MCP server](mcp-server.md) | Connect an MCP client to `remove-ai-watermarks mcp`, or publish it to the MCP registry. |
|
||||
| [Watermarking landscape](watermarking-landscape.md) | Vendor signals and detection approaches. |
|
||||
| [SynthID technical reference](synthid.md) | Mechanism, provenance, robustness, regeneration. |
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# MCP server
|
||||
|
||||
`remove-ai-watermarks mcp` runs a [Model Context Protocol](https://modelcontextprotocol.io/)
|
||||
server on stdin and stdout, so an agent client can identify and clean the
|
||||
user's own images and video without a shell. It is a thin wrapper: each tool
|
||||
runs one command of this CLI in a child process and returns that command's exit
|
||||
code, output path and text.
|
||||
|
||||
The subcommand ships in the first release after 0.45.1. The server uses only
|
||||
the Python standard library, so the default package runs it. The commands behind
|
||||
a tool need the same extras they need on the command line
|
||||
([CLI guide](cli.md#command-dependency-map)). A missing extra comes back as a
|
||||
tool error carrying the CLI's install hint.
|
||||
|
||||
## Client configuration
|
||||
|
||||
Most clients take a command and arguments. With [uv](https://docs.astral.sh/uv/):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"remove-ai-watermarks": {
|
||||
"command": "uvx",
|
||||
"args": ["remove-ai-watermarks", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To make visible-mark removal and pixel detectors available, name the extras:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"remove-ai-watermarks": {
|
||||
"command": "uvx",
|
||||
"args": ["--from", "remove-ai-watermarks[visible,video]", "remove-ai-watermarks", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An existing installation can be started directly as `remove-ai-watermarks mcp`.
|
||||
Paths in tool calls are read and written on the machine that runs the server.
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Runs | Writes |
|
||||
| --- | --- | --- |
|
||||
| `identify` | `identify --json`, or `video identify --json` for a video suffix; `check_visible: false` adds `--no-visible` | Nothing |
|
||||
| `remove` | `metadata --remove`, `visible` or `all` for an image; `video metadata --remove`, `video visible` or `video all` for a video | One output file, `<name>_clean` beside the source unless `output` is given |
|
||||
| `batch` | `batch --mode` for images or `video batch --mode` for video | A separate output directory, `<dir>_clean` unless `output_dir` is given |
|
||||
|
||||
`mode` is `metadata` (the default), `visible` or `all`. For an image, `all`
|
||||
adds the invisible-watermark scrub where the installed extras and a CUDA GPU
|
||||
allow it; `video all` removes visible marks and metadata only. The server never
|
||||
writes over an input: an `output` equal to the source, or an `output_dir` equal
|
||||
to the input directory, is refused before anything runs.
|
||||
|
||||
Exit codes keep their CLI meaning. `0` and `2` are results, not tool errors, and
|
||||
`2` means no supported signal was found, which is unknown rather than clean.
|
||||
Click also exits `2` on a usage error such as a missing file; its `Usage:` line on
|
||||
stderr marks that case as a tool error. Every other nonzero exit code is returned
|
||||
with `isError: true` and the command's stderr.
|
||||
|
||||
## Scope
|
||||
|
||||
The server carries the project's intended-use boundary in its `instructions`:
|
||||
it is for content the user generated or edited themselves, and not for stock,
|
||||
agency, marketplace or other marks that protect a third party's asset. See
|
||||
[legal and safety](legal-and-safety.md).
|
||||
|
||||
## Protocol
|
||||
|
||||
The server answers the initialize handshake for protocol versions 2025-11-25,
|
||||
2025-06-18, 2025-03-26 and 2024-11-05, and lists its tools with input schemas
|
||||
and annotations: `identify` is read-only, and `remove` and `batch` write new
|
||||
files without destroying inputs. It does not implement `server/discover`. A
|
||||
client that probes it first, such as the official Python SDK's `Client` in its
|
||||
default mode, receives method-not-found and falls back to the handshake.
|
||||
`tests/test_mcp_server.py` runs that client against the real subcommand.
|
||||
|
||||
## Registry listing
|
||||
|
||||
[`server.json`](../server.json) describes the PyPI package for the
|
||||
[official MCP registry](https://registry.modelcontextprotocol.io/) under
|
||||
`io.github.wiltodelta/remove-ai-watermarks`. The registry verifies ownership by
|
||||
the `mcp-name:` marker in `README.md`, which becomes the PyPI description, so it
|
||||
can only verify a release whose README carries that marker.
|
||||
[`glama.json`](../glama.json) names the maintainer for Glama. Publishing steps
|
||||
are in [release and distribution](release-and-distribution.md#mcp-registry).
|
||||
@@ -24,9 +24,10 @@ The package version appears in:
|
||||
|
||||
- `pyproject.toml`;
|
||||
- `src/remove_ai_watermarks/__init__.py`;
|
||||
- both `version` fields in `server.json`;
|
||||
- the root package entry generated in `uv.lock`.
|
||||
|
||||
Update the first two, then refresh the lock file with uv. Do not edit a
|
||||
Update the first three, then refresh the lock file with uv. Do not edit a
|
||||
line-number-specific location in `uv.lock`; its package order changes.
|
||||
|
||||
Before choosing a version, verify it is absent from git tags, GitHub Releases,
|
||||
@@ -159,7 +160,10 @@ The Agent Skill and Claude plugin live in this repository:
|
||||
is the Claude plugin manifest, with source `./skills` so an install copies
|
||||
only the skill tree;
|
||||
- [`.claude-plugin/marketplace.json`](../.claude-plugin/marketplace.json)
|
||||
makes the repository a Claude Code marketplace.
|
||||
makes the repository a Claude Code marketplace;
|
||||
- [`.codex-plugin/plugin.json`](../.codex-plugin/plugin.json) is the Codex
|
||||
plugin manifest, pointing at the same `./skills/` tree, with its icon in
|
||||
[`assets/icon.svg`](../assets/icon.svg).
|
||||
|
||||
They are not a fifth required release surface. A CLI change that affects
|
||||
command routing, extras, exit codes, mark keys, or the intended-use boundary
|
||||
@@ -215,6 +219,26 @@ the catalog has not accepted the initial submission. The catalog-specific
|
||||
submission state and links live in
|
||||
[`docs/agent-skill.md`](agent-skill.md#where-the-skill-can-actually-be-listed).
|
||||
|
||||
## MCP registry
|
||||
|
||||
[`server.json`](../server.json) lists the PyPI package in the
|
||||
[official MCP registry](https://registry.modelcontextprotocol.io/) as
|
||||
`io.github.wiltodelta/remove-ai-watermarks`, started as
|
||||
`uvx remove-ai-watermarks mcp`. The registry accepts it only after PyPI serves a
|
||||
release whose description, the README, carries the `mcp-name:` marker, and
|
||||
`tests/test_mcp_server.py` pins `server.json` to the package version. The listing
|
||||
is not a release surface that blocks a release; after PyPI publishes a version:
|
||||
|
||||
1. `mcp-publisher login github` with an account that owns `wiltodelta`;
|
||||
2. `mcp-publisher validate` and then `mcp-publisher publish` from the
|
||||
repository root;
|
||||
3. read the entry back from
|
||||
`https://registry.modelcontextprotocol.io/v0/servers?search=io.github.wiltodelta/remove-ai-watermarks`
|
||||
and check its version.
|
||||
|
||||
[`glama.json`](../glama.json) names the maintainer for Glama's claim flow.
|
||||
Client setup and the tool contract are in [the MCP server guide](mcp-server.md).
|
||||
|
||||
## Release verification
|
||||
|
||||
Forensic transports are versioned independently from the package. Before publishing
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://glama.ai/mcp/schemas/server.json",
|
||||
"maintainers": ["wiltodelta"]
|
||||
}
|
||||
@@ -205,6 +205,9 @@ dev = [
|
||||
"httpx>=0.28,<1",
|
||||
# Development-only isolated browser automation for public provider oracles.
|
||||
"playwright>=1.62,<2",
|
||||
# The official MCP client, used only to test `remove-ai-watermarks mcp`
|
||||
# against the reference implementation. The server itself is stdlib-only.
|
||||
"mcp>=2.3,<3",
|
||||
"pytest>=8.0.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-xdist>=3.5.0",
|
||||
|
||||
@@ -121,7 +121,7 @@ def load_pinned_models() -> tuple[object, object]:
|
||||
raw = convert_state_dict_for_scriptable_model(raw)
|
||||
model = create(config, device="cpu") # type: ignore[call-arg]
|
||||
load_state_dict(model, state_dict=raw)
|
||||
model.eval()
|
||||
model.train(False)
|
||||
return model
|
||||
|
||||
generator = load(GENERATOR_CARD, "generator_base.pth", AudioSealWMConfig, create_generator)
|
||||
|
||||
@@ -51,7 +51,7 @@ def export_onnx(checkpoint: Path, output: Path) -> None:
|
||||
if output.exists():
|
||||
raise SystemExit(f"refusing to overwrite existing output: {output}")
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
model = VisionEmbedding(load_headed_clip(checkpoint, torch.device("cpu"))).eval()
|
||||
model = VisionEmbedding(load_headed_clip(checkpoint, torch.device("cpu"))).train(False)
|
||||
export_options: dict[str, Any] = {}
|
||||
if "dynamo" in inspect.signature(torch.onnx.export).parameters:
|
||||
export_options["dynamo"] = False
|
||||
|
||||
@@ -252,7 +252,7 @@ def _lpips_model() -> tuple[Any, Any]:
|
||||
import torch
|
||||
|
||||
model = lpips.LPIPS(net="alex", verbose=False)
|
||||
model.eval()
|
||||
model.train(False)
|
||||
return model, torch
|
||||
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ def load_model() -> object:
|
||||
raise RuntimeError(
|
||||
f"PixelSeal checkpoint/source mismatch: missing={loaded.missing_keys}, unexpected={loaded.unexpected_keys}"
|
||||
)
|
||||
model.eval()
|
||||
model.train(False)
|
||||
return model
|
||||
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ def _train_detector(emb: dict[str, np.ndarray], catalog: dict) -> tuple[nn.Modul
|
||||
optimizer.zero_grad()
|
||||
loss_fn(model(features).squeeze(-1), labels).backward()
|
||||
optimizer.step()
|
||||
model.eval()
|
||||
model.train(False)
|
||||
dev = np.stack(
|
||||
[
|
||||
emb[row["sha256"]]
|
||||
@@ -161,7 +161,7 @@ def _train_provider(feat: dict[str, np.ndarray], catalog: dict) -> dict[str, nn.
|
||||
optimizer.zero_grad()
|
||||
_focal_loss(model(features).squeeze(-1), labels).backward()
|
||||
optimizer.step()
|
||||
heads[name] = model.eval()
|
||||
heads[name] = model.train(False)
|
||||
log.info("provider %s pos=%d", name, len(positive))
|
||||
return heads
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ def load_model() -> object:
|
||||
import torch
|
||||
|
||||
model = torch.jit.load(str(checkpoint_path()), map_location="cpu")
|
||||
model.eval()
|
||||
model.train(False)
|
||||
return model
|
||||
|
||||
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
||||
"name": "io.github.wiltodelta/remove-ai-watermarks",
|
||||
"title": "Remove AI Watermarks",
|
||||
"description": "Identify and remove AI watermarks and provenance metadata from your own images and video.",
|
||||
"version": "0.45.1",
|
||||
"repository": {
|
||||
"url": "https://github.com/wiltodelta/remove-ai-watermarks",
|
||||
"source": "github"
|
||||
},
|
||||
"websiteUrl": "https://github.com/wiltodelta/remove-ai-watermarks/blob/main/docs/mcp-server.md",
|
||||
"packages": [
|
||||
{
|
||||
"registryType": "pypi",
|
||||
"registryBaseUrl": "https://pypi.org",
|
||||
"identifier": "remove-ai-watermarks",
|
||||
"version": "0.45.1",
|
||||
"runtimeHint": "uvx",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
},
|
||||
"packageArguments": [
|
||||
{
|
||||
"type": "positional",
|
||||
"value": "mcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "remove-ai-watermarks",
|
||||
"displayName": "Remove AI Watermarks",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.12",
|
||||
"description": "Identify and remove AI provenance marks from images and video the user generated or edited, using the remove-ai-watermarks CLI.",
|
||||
"author": {
|
||||
"name": "wiltodelta",
|
||||
|
||||
@@ -5,7 +5,7 @@ license: Apache-2.0
|
||||
compatibility: Python 3.11-3.14. Invisible image removal needs NVIDIA CUDA. Some video writes need ffmpeg. Installer may be uv, pipx, or pip.
|
||||
metadata:
|
||||
author: wiltodelta
|
||||
version: "1.0.11"
|
||||
version: "1.0.12"
|
||||
homepage: https://raiw.cc
|
||||
repository: https://github.com/wiltodelta/remove-ai-watermarks
|
||||
---
|
||||
|
||||
@@ -138,6 +138,13 @@ user names a device, and never to claim a device the probe did not report.
|
||||
Without `-o`, video commands write `<source>_clean` and keep the original.
|
||||
Output extension must match the input container.
|
||||
|
||||
## MCP server
|
||||
|
||||
`remove-ai-watermarks mcp` (new after CLI 0.45.1) serves `identify`, `remove`
|
||||
and `batch` as MCP tools over stdin and stdout for MCP clients. It blocks
|
||||
waiting for protocol messages, so do not run it from this skill; call the
|
||||
commands above directly.
|
||||
|
||||
## Exit codes
|
||||
|
||||
| Code | Meaning |
|
||||
|
||||
@@ -67,7 +67,7 @@ def load_headed_clip(checkpoint: Path, device: torch.device) -> HeadedCLIP:
|
||||
model = HeadedCLIP(clip)
|
||||
state = torch.load(checkpoint, map_location="cpu", weights_only=True)
|
||||
model.load_state_dict(state, strict=True)
|
||||
return model.to(device).eval()
|
||||
return model.to(device).train(False)
|
||||
|
||||
|
||||
def load_processor() -> Any:
|
||||
|
||||
@@ -823,7 +823,7 @@ class TwoStageZImagePipeline:
|
||||
torch_dtype=self._face_stage_dtype(),
|
||||
**kwargs,
|
||||
).to(self.device)
|
||||
model.eval()
|
||||
model.train(False)
|
||||
self._sam_model = model
|
||||
self._sam_processor = processor
|
||||
return model, processor
|
||||
|
||||
@@ -467,7 +467,7 @@ def _load_runtime(device: Any, backend: ClassifierBackend = "torch") -> _Runtime
|
||||
probe = np.load(_weight_path(folder, PROBE_FILE))
|
||||
detector = _build_detector()
|
||||
detector.load_state_dict(torch.load(_weight_path(folder, DETECTOR_FILE), map_location="cpu", weights_only=True))
|
||||
detector.to(device).eval()
|
||||
detector.to(device).train(False)
|
||||
packed = torch.load(_weight_path(folder, PROVIDER_FILE), map_location="cpu", weights_only=True)
|
||||
heads = {}
|
||||
for name in PROVIDER_LABELS:
|
||||
@@ -476,7 +476,7 @@ def _load_runtime(device: Any, backend: ClassifierBackend = "torch") -> _Runtime
|
||||
continue
|
||||
head = _build_provider_head()
|
||||
head.load_state_dict(packed[name])
|
||||
heads[name] = head.to(device).eval()
|
||||
heads[name] = head.to(device).train(False)
|
||||
ridge_threshold = float(probe["thr_oi_1pct"])
|
||||
mlp_threshold, provider_margin = _operating_point(folder)
|
||||
log.info("loaded photo-classify freeze from %s", folder)
|
||||
|
||||
@@ -1911,5 +1911,19 @@ def cmd_batch(
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
# ── MCP server ──
|
||||
@main.command("mcp")
|
||||
def cmd_mcp() -> None:
|
||||
"""Run a Model Context Protocol server on stdin/stdout.
|
||||
|
||||
Exposes identify, remove and batch as MCP tools for agent clients. Each tool
|
||||
runs one command of this CLI, so results and exit codes match it. Start it as
|
||||
`uvx remove-ai-watermarks mcp` from the client's server configuration.
|
||||
"""
|
||||
from remove_ai_watermarks.mcp_server import serve
|
||||
|
||||
serve()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,297 @@
|
||||
"""Model Context Protocol server over stdio, a thin wrapper around this package's CLI.
|
||||
|
||||
``remove-ai-watermarks mcp`` speaks newline-delimited JSON-RPC 2.0 on stdin and
|
||||
stdout with the initialize handshake. Each tool call runs one CLI command in a
|
||||
child process of the same interpreter, so a tool returns exactly what that command
|
||||
would: its exit code, its output file and its text. Nothing but protocol messages
|
||||
reaches stdout, and a crash inside a command cannot corrupt the stream.
|
||||
|
||||
The server needs no dependency beyond the standard library, so the bare package
|
||||
runs it through ``uvx remove-ai-watermarks mcp``. Commands that need an optional
|
||||
extra fail with the CLI's own install hint, returned as a tool error.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import IO, Any, cast
|
||||
|
||||
from remove_ai_watermarks import __version__
|
||||
from remove_ai_watermarks.video import VIDEO_EXTENSIONS
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Newest first. A client asking for an unlisted version gets the newest, and
|
||||
# decides for itself whether it can continue.
|
||||
PROTOCOL_VERSIONS = ("2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05")
|
||||
|
||||
INSTRUCTIONS = (
|
||||
"Identify and remove AI provenance marks from images and video the user generated "
|
||||
"or edited themselves: C2PA, IPTC, EXIF and TC260 metadata, visible AI-generator "
|
||||
"labels, and supported invisible watermarks. Do not use it on stock, agency, "
|
||||
"marketplace or other third-party marks that protect someone else's asset. A "
|
||||
"result of 'unknown' or exit code 2 means no supported signal was found, not "
|
||||
"that the file is clean. Paths are read on the machine running the server."
|
||||
)
|
||||
|
||||
_MODES = ("metadata", "visible", "all")
|
||||
|
||||
TOOLS: list[dict[str, Any]] = [
|
||||
{
|
||||
"name": "identify",
|
||||
"title": "Identify AI provenance",
|
||||
"description": (
|
||||
"Report which AI provenance signals an image or video carries: metadata "
|
||||
"(C2PA, IPTC, EXIF, TC260), visible AI-generator labels, and supported local "
|
||||
"invisible-watermark decoders. Read-only. Video is detected by file suffix."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Image or video file to inspect."},
|
||||
"check_visible": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Run the pixel and visible-mark detectors. False reads metadata only.",
|
||||
},
|
||||
},
|
||||
"required": ["path"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
"annotations": {"readOnlyHint": True, "openWorldHint": False},
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"title": "Remove AI watermarks from one file",
|
||||
"description": (
|
||||
"Write a cleaned copy of an image or video the user generated or edited. "
|
||||
"mode=metadata strips AI metadata without touching pixels or re-encoding video; "
|
||||
"mode=visible also removes a known visible AI label; for an image, mode=all adds "
|
||||
"the invisible-watermark scrub where the installed extras and hardware allow it. "
|
||||
"The source is never overwritten; the default output is <name>_clean beside it."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Image or video file to clean."},
|
||||
"mode": {"type": "string", "enum": list(_MODES), "default": "metadata"},
|
||||
"output": {"type": "string", "description": "Output file. Must differ from the source."},
|
||||
},
|
||||
"required": ["path"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
"annotations": {"readOnlyHint": False, "destructiveHint": False, "openWorldHint": False},
|
||||
},
|
||||
{
|
||||
"name": "batch",
|
||||
"title": "Remove AI watermarks from a directory",
|
||||
"description": (
|
||||
"Clean every supported image (media=image) or video (media=video) in a directory "
|
||||
"the user owns, writing results to a separate directory (default <dir>_clean). "
|
||||
"Modes as for the remove tool."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {"type": "string", "description": "Directory of input files."},
|
||||
"mode": {"type": "string", "enum": list(_MODES), "default": "metadata"},
|
||||
"media": {"type": "string", "enum": ["image", "video"], "default": "image"},
|
||||
"output_dir": {"type": "string", "description": "Output directory. Must differ from the input."},
|
||||
},
|
||||
"required": ["directory"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
"annotations": {"readOnlyHint": False, "destructiveHint": False, "openWorldHint": False},
|
||||
},
|
||||
]
|
||||
|
||||
_TOOL_SCHEMAS = {tool["name"]: tool["inputSchema"] for tool in TOOLS}
|
||||
|
||||
Runner = Callable[[list[str]], subprocess.CompletedProcess[str]]
|
||||
|
||||
|
||||
class ToolInputError(ValueError):
|
||||
"""A tool call whose arguments the server refuses before running anything."""
|
||||
|
||||
|
||||
def run_cli(args: list[str]) -> subprocess.CompletedProcess[str]:
|
||||
"""Run one CLI command in a child process of this interpreter."""
|
||||
env = {**os.environ, "COLUMNS": "120", "NO_COLOR": "1", "TERM": "dumb"}
|
||||
command = [sys.executable, "-m", "remove_ai_watermarks.cli", *args]
|
||||
log.info("Running CLI command: %s", command)
|
||||
completed = subprocess.run( # noqa: S603 -- fixed interpreter, argument list, no shell
|
||||
command,
|
||||
stdin=subprocess.DEVNULL,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env,
|
||||
check=False,
|
||||
)
|
||||
log.info("CLI command exited %d: %s", completed.returncode, command)
|
||||
return completed
|
||||
|
||||
|
||||
def _validated(tool: str, arguments: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Check arguments against the tool's own input schema and fill its defaults."""
|
||||
schema = _TOOL_SCHEMAS[tool]
|
||||
properties: dict[str, dict[str, Any]] = schema["properties"]
|
||||
unknown = sorted(set(arguments) - set(properties))
|
||||
if unknown:
|
||||
raise ToolInputError(f"unknown argument(s): {', '.join(unknown)}")
|
||||
values: dict[str, Any] = {}
|
||||
for key, spec in properties.items():
|
||||
if key not in arguments:
|
||||
if key in schema["required"]:
|
||||
raise ToolInputError(f"'{key}' is required")
|
||||
if "default" in spec:
|
||||
values[key] = spec["default"]
|
||||
continue
|
||||
value = arguments[key]
|
||||
if spec["type"] == "boolean" and not isinstance(value, bool):
|
||||
raise ToolInputError(f"'{key}' must be a boolean")
|
||||
if spec["type"] == "string" and (not isinstance(value, str) or not value.strip()):
|
||||
raise ToolInputError(f"'{key}' must be a non-empty string")
|
||||
if "enum" in spec and value not in spec["enum"]:
|
||||
raise ToolInputError(f"'{key}' must be one of {', '.join(spec['enum'])}")
|
||||
values[key] = value
|
||||
return values
|
||||
|
||||
|
||||
def _is_video(path: str) -> bool:
|
||||
return Path(path).suffix.lower() in VIDEO_EXTENSIONS
|
||||
|
||||
|
||||
def _identify_command(values: dict[str, Any]) -> tuple[list[str], str | None]:
|
||||
path = values["path"]
|
||||
prefix = ["video", "identify"] if _is_video(path) else ["identify"]
|
||||
return [*prefix, "--json", *([] if values["check_visible"] else ["--no-visible"]), "--", path], None
|
||||
|
||||
|
||||
def _remove_command(values: dict[str, Any]) -> tuple[list[str], str | None]:
|
||||
path, mode = values["path"], values["mode"]
|
||||
source = Path(path)
|
||||
output = values.get("output") or str(source.with_stem(source.stem + "_clean"))
|
||||
if Path(output).resolve() == source.resolve():
|
||||
raise ToolInputError("'output' must differ from the source; the server never overwrites an input")
|
||||
command = ["metadata", "--remove"] if mode == "metadata" else [mode]
|
||||
return [*(["video"] if _is_video(path) else []), *command, "-o", output, "--", path], output
|
||||
|
||||
|
||||
def _batch_command(values: dict[str, Any]) -> tuple[list[str], str | None]:
|
||||
directory = values["directory"]
|
||||
resolved = Path(directory).resolve()
|
||||
output_dir = values.get("output_dir") or str(resolved.with_name(resolved.name + "_clean"))
|
||||
if Path(output_dir).resolve() == resolved:
|
||||
raise ToolInputError("'output_dir' must differ from the input directory")
|
||||
command = ["video", "batch"] if values["media"] == "video" else ["batch"]
|
||||
return [*command, "--mode", values["mode"], "-o", output_dir, "--", directory], output_dir
|
||||
|
||||
|
||||
_COMMANDS: dict[str, Callable[[dict[str, Any]], tuple[list[str], str | None]]] = {
|
||||
"identify": _identify_command,
|
||||
"remove": _remove_command,
|
||||
"batch": _batch_command,
|
||||
}
|
||||
|
||||
|
||||
def call_tool(name: str, arguments: dict[str, Any], runner: Runner = run_cli) -> dict[str, Any]:
|
||||
"""Run one tool and return an MCP ``tools/call`` result."""
|
||||
try:
|
||||
args, output = _COMMANDS[name](_validated(name, arguments))
|
||||
except ToolInputError as exc:
|
||||
return {"content": [{"type": "text", "text": f"Invalid arguments: {exc}"}], "isError": True}
|
||||
completed = runner(args)
|
||||
code = completed.returncode
|
||||
text = completed.stdout.strip()
|
||||
stderr = completed.stderr.strip()[-4000:]
|
||||
structured: dict[str, Any] = {"command": ["remove-ai-watermarks", *args], "exit_code": code, "output": output}
|
||||
if "--json" in args and code == 0:
|
||||
with contextlib.suppress(json.JSONDecodeError):
|
||||
structured["report"] = json.loads(text)
|
||||
if "report" not in structured:
|
||||
structured["stdout"] = text
|
||||
if stderr:
|
||||
structured["stderr"] = stderr
|
||||
# The CLI's own exit 2 means no supported signal, a result. Click also exits 2 on
|
||||
# a usage error, such as a missing file, and prints its usage line on stderr.
|
||||
no_signal = code == 2 and "Usage:" not in stderr
|
||||
is_error = code != 0 and not no_signal
|
||||
summary = f"exit code {code}"
|
||||
if no_signal:
|
||||
summary += " (no supported signal found; this is unknown, not proof the file is clean)"
|
||||
body = "\n\n".join(part for part in (summary, text, stderr if is_error else "") if part)
|
||||
return {"content": [{"type": "text", "text": body}], "structuredContent": structured, "isError": is_error}
|
||||
|
||||
|
||||
def _error(request_id: object, code: int, message: str) -> dict[str, Any]:
|
||||
return {"jsonrpc": "2.0", "id": request_id, "error": {"code": code, "message": message}}
|
||||
|
||||
|
||||
def _object(value: object) -> dict[str, Any] | None:
|
||||
"""A decoded JSON object, or ``None`` for any other JSON value."""
|
||||
return cast("dict[str, Any]", value) if isinstance(value, dict) else None
|
||||
|
||||
|
||||
def handle(raw: object, runner: Runner = run_cli) -> dict[str, Any] | None:
|
||||
"""Answer one JSON-RPC message; ``None`` for notifications and client responses."""
|
||||
message = _object(raw)
|
||||
if message is None or message.get("jsonrpc") != "2.0":
|
||||
return _error(None, -32600, "Invalid Request")
|
||||
method = message.get("method")
|
||||
if "id" not in message:
|
||||
return None # a notification or a response to nothing this server sent
|
||||
request_id: object = message["id"]
|
||||
if not isinstance(method, str):
|
||||
return _error(request_id, -32600, "Invalid Request")
|
||||
params = _object(message.get("params") or {})
|
||||
if params is None:
|
||||
return _error(request_id, -32602, "params must be an object")
|
||||
|
||||
if method == "initialize":
|
||||
requested = params.get("protocolVersion")
|
||||
result: dict[str, Any] = {
|
||||
"protocolVersion": requested if requested in PROTOCOL_VERSIONS else PROTOCOL_VERSIONS[0],
|
||||
"capabilities": {"tools": {"listChanged": False}},
|
||||
"serverInfo": {"name": "remove-ai-watermarks", "title": "Remove AI Watermarks", "version": __version__},
|
||||
"instructions": INSTRUCTIONS,
|
||||
}
|
||||
elif method == "ping":
|
||||
result = {}
|
||||
elif method == "tools/list":
|
||||
result = {"tools": TOOLS}
|
||||
elif method == "tools/call":
|
||||
name = params.get("name")
|
||||
arguments = _object(params.get("arguments") or {})
|
||||
if not isinstance(name, str) or name not in _COMMANDS:
|
||||
return _error(request_id, -32602, f"Unknown tool: {name}")
|
||||
if arguments is None:
|
||||
return _error(request_id, -32602, "arguments must be an object")
|
||||
result = call_tool(name, arguments, runner)
|
||||
else:
|
||||
return _error(request_id, -32601, f"Method not found: {method}")
|
||||
return {"jsonrpc": "2.0", "id": request_id, "result": result}
|
||||
|
||||
|
||||
def serve(stdin: IO[str] | None = None, stdout: IO[str] | None = None, runner: Runner = run_cli) -> None:
|
||||
"""Serve newline-delimited JSON-RPC until stdin closes."""
|
||||
source: IO[str] = sys.stdin if stdin is None else stdin
|
||||
sink: IO[str] = sys.stdout if stdout is None else stdout
|
||||
for line in source:
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
message = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
response: dict[str, Any] | None = _error(None, -32700, "Parse error")
|
||||
else:
|
||||
response = handle(message, runner)
|
||||
if response is not None:
|
||||
sink.write(json.dumps(response, ensure_ascii=False) + "\n")
|
||||
sink.flush()
|
||||
@@ -135,7 +135,7 @@ def load_video_vae_runtime(
|
||||
dtype = torch.float16 if resolved_device == "cuda" else torch.float32
|
||||
log.info("Loading %s on %s", model, resolved_device)
|
||||
vae = AutoencoderKL.from_pretrained(model, torch_dtype=dtype).to(resolved_device)
|
||||
vae.eval()
|
||||
vae.train(False)
|
||||
vae.enable_slicing()
|
||||
scaling_factor = float(vae.config.scaling_factor)
|
||||
log.info("Latent scaling factor %.5f", scaling_factor)
|
||||
|
||||
@@ -14,6 +14,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
SKILL = ROOT / "skills" / "remove-ai-watermarks" / "SKILL.md"
|
||||
PLUGIN = ROOT / "skills" / ".claude-plugin" / "plugin.json"
|
||||
MARKETPLACE = ROOT / ".claude-plugin" / "marketplace.json"
|
||||
CODEX_PLUGIN = ROOT / ".codex-plugin" / "plugin.json"
|
||||
AGENT_DOC = ROOT / "docs" / "agent-skill.md"
|
||||
INSTALL_DOC = ROOT / "docs" / "installation.md"
|
||||
INSTALL_REFERENCE = ROOT / "skills" / "remove-ai-watermarks" / "references" / "install.md"
|
||||
@@ -541,15 +542,16 @@ def test_probe_minimum_version_is_a_release_that_exists() -> None:
|
||||
assert current >= probe.MIN_CLI_VERSION, "the probe demands a version the project has not released"
|
||||
|
||||
|
||||
def test_skill_version_agrees_across_all_three_manifests() -> None:
|
||||
"""Three files carry this version, and a catalog reads a different one from each."""
|
||||
def test_skill_version_agrees_across_every_manifest() -> None:
|
||||
"""Four files carry this version, and a catalog reads a different one from each."""
|
||||
frontmatter = _frontmatter(SKILL.read_text(encoding="utf-8"))
|
||||
match = re.search(r'(?m)^\s+version:\s*"?([0-9][^"\n]*?)"?\s*$', frontmatter)
|
||||
assert match is not None, "SKILL.md frontmatter no longer carries metadata.version"
|
||||
plugin = json.loads(PLUGIN.read_text(encoding="utf-8"))
|
||||
marketplace = json.loads(MARKETPLACE.read_text(encoding="utf-8"))
|
||||
codex = json.loads(CODEX_PLUGIN.read_text(encoding="utf-8"))
|
||||
|
||||
assert match.group(1) == plugin["version"] == marketplace["metadata"]["version"]
|
||||
assert match.group(1) == plugin["version"] == marketplace["metadata"]["version"] == codex["version"]
|
||||
|
||||
|
||||
def test_a_bad_invocation_shares_the_no_signal_exit_code(tmp_path: Path) -> None:
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""The Codex plugin manifest, held to the checks a Codex plugin catalog runs on it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MANIFEST = ROOT / ".codex-plugin" / "plugin.json"
|
||||
CLAUDE_PLUGIN = ROOT / "skills" / ".claude-plugin" / "plugin.json"
|
||||
|
||||
|
||||
def _manifest() -> dict:
|
||||
return json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def test_manifest_carries_every_field_the_catalog_validator_requires() -> None:
|
||||
manifest = _manifest()
|
||||
for field in ("name", "version", "description", "repository", "license"):
|
||||
assert str(manifest.get(field, "")).strip(), field
|
||||
interface = manifest["interface"]
|
||||
for field in ("displayName", "shortDescription", "composerIcon"):
|
||||
assert str(interface.get(field, "")).strip(), field
|
||||
assert re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", manifest["name"])
|
||||
assert re.fullmatch(r"\d+\.\d+\.\d+", manifest["version"])
|
||||
assert re.fullmatch(r"#[0-9A-Fa-f]{6}", interface["brandColor"])
|
||||
|
||||
|
||||
def test_manifest_describes_the_same_plugin_as_the_claude_manifest() -> None:
|
||||
manifest = _manifest()
|
||||
claude = json.loads(CLAUDE_PLUGIN.read_text(encoding="utf-8"))
|
||||
for field in ("name", "description", "repository", "license", "homepage"):
|
||||
assert manifest[field] == claude[field], field
|
||||
|
||||
|
||||
def test_every_declared_path_stays_inside_the_repository() -> None:
|
||||
manifest = _manifest()
|
||||
interface = manifest["interface"]
|
||||
paths = [manifest["skills"], interface["composerIcon"], interface["logo"]]
|
||||
for declared in paths:
|
||||
assert declared.startswith("./"), declared
|
||||
resolved = (ROOT / declared).resolve()
|
||||
assert resolved.is_relative_to(ROOT), declared
|
||||
assert resolved.exists(), declared
|
||||
assert (ROOT / manifest["skills"] / "remove-ai-watermarks" / "SKILL.md").is_file()
|
||||
|
||||
|
||||
def test_icon_is_a_small_self_contained_svg() -> None:
|
||||
icon = ROOT / _manifest()["interface"]["composerIcon"]
|
||||
content = icon.read_text(encoding="utf-8")
|
||||
assert icon.suffix == ".svg"
|
||||
assert icon.stat().st_size <= 50 * 1024
|
||||
for forbidden in ("data:image/", "base64", "TODO", "PLACEHOLDER"):
|
||||
assert forbidden not in content, forbidden
|
||||
|
||||
|
||||
def test_no_tracked_code_matches_the_scanner_dynamic_execution_rule() -> None:
|
||||
"""The catalog's scanner reports a high finding for any call spelled like
|
||||
Python's built-in evaluator in its code files, and that rule cannot tell it from
|
||||
the PyTorch module method of the same name. Its scan job is advisory, but the
|
||||
finding still lands on the listing PR, so the code switches modules to inference
|
||||
with the equivalent ``module.train(False)``."""
|
||||
tracked = subprocess.run(
|
||||
["git", "ls-files", "*.py", "*.js", "*.mjs", "*.cjs", "*.ts", "*.tsx", "*.jsx"], # noqa: S607
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout.split()
|
||||
assert tracked
|
||||
rule = re.compile(r"\beval\s*\(")
|
||||
offenders = [name for name in tracked if rule.search((ROOT / name).read_text(encoding="utf-8", errors="ignore"))]
|
||||
assert offenders == []
|
||||
@@ -0,0 +1,272 @@
|
||||
"""`remove-ai-watermarks mcp`: the protocol layer, the CLI mapping, and real clients."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from remove_ai_watermarks import mcp_server
|
||||
from remove_ai_watermarks.metadata import KLING_SEI_TAG
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
KLING_MP4 = ROOT / "data/fixtures/provenance/higgsfield-kling-3-0-turbo.mp4"
|
||||
SERVER = [sys.executable, "-m", "remove_ai_watermarks.cli", "mcp"]
|
||||
|
||||
|
||||
class _Recorder:
|
||||
"""Stands in for the CLI child process and records each argument list."""
|
||||
|
||||
def __init__(self, returncode: int = 0, stdout: str = "", stderr: str = "") -> None:
|
||||
self.calls: list[list[str]] = []
|
||||
self._result = (returncode, stdout, stderr)
|
||||
|
||||
def __call__(self, args: list[str]) -> subprocess.CompletedProcess[str]:
|
||||
self.calls.append(args)
|
||||
returncode, stdout, stderr = self._result
|
||||
return subprocess.CompletedProcess(args, returncode, stdout, stderr)
|
||||
|
||||
|
||||
def _request(method: str, params: dict | None = None, request_id: int = 1) -> dict:
|
||||
message: dict = {"jsonrpc": "2.0", "id": request_id, "method": method}
|
||||
if params is not None:
|
||||
message["params"] = params
|
||||
return message
|
||||
|
||||
|
||||
def _assert_the_cli_accepts(args: list[str]) -> None:
|
||||
"""Every subcommand and flag a tool builds must exist in the real Click tree."""
|
||||
import click
|
||||
|
||||
from remove_ai_watermarks.cli import main
|
||||
|
||||
command: click.Command = main
|
||||
position = 0
|
||||
while isinstance(command, click.Group):
|
||||
command = command.commands[args[position]]
|
||||
position += 1
|
||||
options = {option for param in command.params for option in (*param.opts, *param.secondary_opts)}
|
||||
flags = [arg for arg in args[position : args.index("--")] if arg.startswith("-")]
|
||||
assert set(flags) <= options, (args, sorted(set(flags) - options))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("requested", "negotiated"),
|
||||
[("2025-06-18", "2025-06-18"), ("2024-11-05", "2024-11-05"), ("2099-01-01", mcp_server.PROTOCOL_VERSIONS[0])],
|
||||
)
|
||||
def test_initialize_negotiates_a_supported_protocol_version(requested: str, negotiated: str) -> None:
|
||||
response = mcp_server.handle(_request("initialize", {"protocolVersion": requested, "capabilities": {}}))
|
||||
|
||||
assert response is not None
|
||||
assert response["result"]["protocolVersion"] == negotiated
|
||||
assert response["result"]["capabilities"] == {"tools": {"listChanged": False}}
|
||||
assert "third-party" in response["result"]["instructions"]
|
||||
|
||||
|
||||
def test_notifications_get_no_reply_and_unknown_methods_get_method_not_found() -> None:
|
||||
assert mcp_server.handle({"jsonrpc": "2.0", "method": "notifications/initialized"}) is None
|
||||
# A modern client probes server/discover first and falls back to initialize on this error.
|
||||
response = mcp_server.handle(_request("server/discover"))
|
||||
assert response is not None
|
||||
assert response["error"]["code"] == -32601
|
||||
|
||||
|
||||
def test_tools_list_exposes_exactly_the_three_tools() -> None:
|
||||
response = mcp_server.handle(_request("tools/list"))
|
||||
|
||||
assert response is not None
|
||||
tools = response["result"]["tools"]
|
||||
assert [tool["name"] for tool in tools] == ["identify", "remove", "batch"]
|
||||
assert tools[0]["annotations"]["readOnlyHint"] is True
|
||||
for tool in tools:
|
||||
assert tool["inputSchema"]["type"] == "object"
|
||||
assert tool["inputSchema"]["additionalProperties"] is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("arguments", "expected"),
|
||||
[
|
||||
({"path": "a.png"}, ["identify", "--json", "--", "a.png"]),
|
||||
({"path": "a.png", "check_visible": False}, ["identify", "--json", "--no-visible", "--", "a.png"]),
|
||||
({"path": "clip.MP4"}, ["video", "identify", "--json", "--", "clip.MP4"]),
|
||||
],
|
||||
)
|
||||
def test_identify_maps_to_the_cli(arguments: dict, expected: list[str]) -> None:
|
||||
runner = _Recorder(stdout='{"is_ai_generated": true}')
|
||||
|
||||
result = mcp_server.call_tool("identify", arguments, runner)
|
||||
|
||||
assert runner.calls == [expected]
|
||||
_assert_the_cli_accepts(expected)
|
||||
assert result["isError"] is False
|
||||
assert result["structuredContent"]["report"] == {"is_ai_generated": True}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("arguments", "expected"),
|
||||
[
|
||||
({"path": "d/a.png"}, ["metadata", "--remove", "-o", "d/a_clean.png", "--", "d/a.png"]),
|
||||
({"path": "a.jpg", "mode": "visible"}, ["visible", "-o", "a_clean.jpg", "--", "a.jpg"]),
|
||||
({"path": "a.png", "mode": "all", "output": "o.png"}, ["all", "-o", "o.png", "--", "a.png"]),
|
||||
({"path": "c.mov"}, ["video", "metadata", "--remove", "-o", "c_clean.mov", "--", "c.mov"]),
|
||||
({"path": "c.mp4", "mode": "visible"}, ["video", "visible", "-o", "c_clean.mp4", "--", "c.mp4"]),
|
||||
],
|
||||
)
|
||||
def test_remove_maps_to_the_cli_and_always_names_an_output(arguments: dict, expected: list[str]) -> None:
|
||||
runner = _Recorder()
|
||||
|
||||
result = mcp_server.call_tool("remove", arguments, runner)
|
||||
|
||||
assert runner.calls == [expected]
|
||||
_assert_the_cli_accepts(expected)
|
||||
assert result["structuredContent"]["output"] == expected[expected.index("-o") + 1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("arguments", "expected"),
|
||||
[
|
||||
({"directory": "in"}, ["batch", "--mode", "metadata", "-o", str(Path("in_clean").resolve()), "--", "in"]),
|
||||
(
|
||||
{"directory": "in", "media": "video", "mode": "visible", "output_dir": "out"},
|
||||
["video", "batch", "--mode", "visible", "-o", "out", "--", "in"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_batch_maps_to_the_cli(arguments: dict, expected: list[str]) -> None:
|
||||
runner = _Recorder()
|
||||
|
||||
result = mcp_server.call_tool("batch", arguments, runner)
|
||||
|
||||
assert runner.calls == [expected]
|
||||
assert result["structuredContent"]["output"] == expected[expected.index("-o") + 1]
|
||||
_assert_the_cli_accepts(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("tool", "arguments"),
|
||||
[
|
||||
("remove", {"path": "a.png", "output": "./a.png"}),
|
||||
("remove", {"path": "a.png", "mode": "invisible"}),
|
||||
("remove", {"path": ""}),
|
||||
("remove", {}),
|
||||
("identify", {"path": "a.png", "check_visible": "no"}),
|
||||
("identify", {"path": "a.png", "shell": "rm -rf /"}),
|
||||
("batch", {"directory": "in", "output_dir": "in"}),
|
||||
],
|
||||
)
|
||||
def test_invalid_arguments_are_refused_before_anything_runs(tool: str, arguments: dict) -> None:
|
||||
runner = _Recorder()
|
||||
|
||||
result = mcp_server.call_tool(tool, arguments, runner)
|
||||
|
||||
assert runner.calls == []
|
||||
assert result["isError"] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("returncode", "stderr", "is_error"),
|
||||
[
|
||||
(0, "", False),
|
||||
(2, "", False),
|
||||
(1, "Error: boom", True),
|
||||
# Click's own usage errors also exit 2; they are failures, not a no-signal verdict.
|
||||
(2, "Usage: cli metadata [OPTIONS] SOURCE\nError: Invalid value for 'SOURCE'", True),
|
||||
],
|
||||
)
|
||||
def test_only_failures_are_tool_errors(returncode: int, stderr: str, is_error: bool) -> None:
|
||||
runner = _Recorder(returncode=returncode, stdout="table", stderr=stderr)
|
||||
|
||||
result = mcp_server.call_tool("remove", {"path": "a.png"}, runner)
|
||||
|
||||
assert result["isError"] is is_error
|
||||
assert result["structuredContent"]["exit_code"] == returncode
|
||||
assert ("not proof the file is clean" in result["content"][0]["text"]) is (returncode == 2 and not is_error)
|
||||
if is_error:
|
||||
assert stderr.splitlines()[-1] in result["content"][0]["text"]
|
||||
|
||||
|
||||
def test_serve_answers_line_by_line_and_reports_parse_errors() -> None:
|
||||
stdin = io.StringIO(
|
||||
json.dumps(_request("ping", request_id=7))
|
||||
+ "\n\nnot json\n"
|
||||
+ json.dumps({"jsonrpc": "2.0", "method": "notifications/initialized"})
|
||||
+ "\n"
|
||||
)
|
||||
stdout = io.StringIO()
|
||||
|
||||
mcp_server.serve(stdin, stdout, _Recorder())
|
||||
|
||||
replies = [json.loads(line) for line in stdout.getvalue().splitlines()]
|
||||
assert replies == [
|
||||
{"jsonrpc": "2.0", "id": 7, "result": {}},
|
||||
{"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}},
|
||||
]
|
||||
|
||||
|
||||
def test_the_cli_subcommand_serves_real_commands_over_stdio(tmp_path: Path) -> None:
|
||||
"""End to end through the installed CLI: every stdout line must be a protocol message."""
|
||||
output = tmp_path / "clean.mp4"
|
||||
messages = [
|
||||
_request("initialize", {"protocolVersion": "2025-06-18", "capabilities": {}}, 1),
|
||||
{"jsonrpc": "2.0", "method": "notifications/initialized"},
|
||||
_request("tools/call", {"name": "identify", "arguments": {"path": str(KLING_MP4), "check_visible": False}}, 2),
|
||||
_request("tools/call", {"name": "remove", "arguments": {"path": str(KLING_MP4), "output": str(output)}}, 3),
|
||||
]
|
||||
completed = subprocess.run( # noqa: S603
|
||||
SERVER,
|
||||
input="".join(json.dumps(message) + "\n" for message in messages),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
timeout=300,
|
||||
)
|
||||
|
||||
replies = {reply["id"]: reply for reply in map(json.loads, completed.stdout.splitlines())}
|
||||
assert sorted(replies) == [1, 2, 3]
|
||||
report = replies[2]["result"]["structuredContent"]["report"]
|
||||
assert report["platform"] == "Kuaishou Kling AI"
|
||||
assert "kling_sei" in report["metadata_markers"]
|
||||
assert replies[3]["result"]["isError"] is False
|
||||
assert KLING_SEI_TAG not in output.read_bytes()
|
||||
|
||||
|
||||
def test_the_official_sdk_client_connects_and_calls_a_tool() -> None:
|
||||
"""The reference client probes server/discover first, then falls back to initialize."""
|
||||
anyio = pytest.importorskip("anyio")
|
||||
mcp = pytest.importorskip("mcp")
|
||||
|
||||
async def session() -> tuple[list[str], dict]:
|
||||
params = mcp.StdioServerParameters(command=SERVER[0], args=SERVER[1:], cwd=str(ROOT))
|
||||
async with mcp.Client(params) as client:
|
||||
tools = await client.list_tools()
|
||||
result = await client.call_tool("identify", {"path": str(KLING_MP4), "check_visible": False})
|
||||
return [tool.name for tool in tools.tools], result.structured_content
|
||||
|
||||
names, structured = anyio.run(session)
|
||||
|
||||
assert names == ["identify", "remove", "batch"]
|
||||
assert structured["report"]["platform"] == "Kuaishou Kling AI"
|
||||
|
||||
|
||||
def test_registry_manifest_matches_the_package_and_the_readme_marker() -> None:
|
||||
"""The registry launches `uvx <identifier>==<version> <packageArguments>` and verifies
|
||||
ownership by the README's `mcp-name:` marker, so each of those must agree."""
|
||||
from click.testing import CliRunner
|
||||
|
||||
from remove_ai_watermarks import __version__
|
||||
from remove_ai_watermarks.cli import main
|
||||
|
||||
manifest = json.loads((ROOT / "server.json").read_text(encoding="utf-8"))
|
||||
(package,) = manifest["packages"]
|
||||
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||
|
||||
assert manifest["version"] == package["version"] == __version__
|
||||
assert package["identifier"] == "remove-ai-watermarks"
|
||||
assert re.search(rf"mcp-name: {re.escape(manifest['name'])}(?=\s|-->|<)", readme)
|
||||
args = [argument["value"] for argument in package["packageArguments"]]
|
||||
assert CliRunner().invoke(main, [*args, "--help"]).exit_code == 0
|
||||
@@ -824,7 +824,7 @@ name = "cuda-bindings"
|
||||
version = "13.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cuda-pathfinder" },
|
||||
{ name = "cuda-pathfinder", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/3a/06569edfd5db020c1ff7c0361268a3eaa60ead0c0ccca460fef69d68022f/cuda_bindings-13.4.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df8b3767facca8acde216460df684dfc3826d519096c13adfd3030a55870dc79", size = 6494098, upload-time = "2026-09-23T02:21:50.472Z" },
|
||||
@@ -857,43 +857,43 @@ wheels = [
|
||||
|
||||
[package.optional-dependencies]
|
||||
cublas = [
|
||||
{ name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
cudart = [
|
||||
{ name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
cufft = [
|
||||
{ name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
cufile = [
|
||||
{ name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
cupti = [
|
||||
{ name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
curand = [
|
||||
{ name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
cusolver = [
|
||||
{ name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
cusparse = [
|
||||
{ name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
nvjitlink = [
|
||||
{ name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
nvrtc = [
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
nvtx = [
|
||||
{ name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
|
||||
{ name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1220,8 +1220,8 @@ name = "httpcore2"
|
||||
version = "2.13.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "h11" },
|
||||
{ name = "truststore" },
|
||||
{ name = "h11", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" }
|
||||
wheels = [
|
||||
@@ -1475,6 +1475,33 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/8b/d76219ebdbcf3d4209d9d21a0810db4c8d0a6f88e3ee87d30bdea4e90d30/jiter-0.17.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c0bf24c72fd0491405dce5d40194f2070e9021ce648c1a1d46234b93d848ff", size = 307147, upload-time = "2026-09-12T15:14:12.897Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "4.26.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "jsonschema-specifications" },
|
||||
{ name = "referencing" },
|
||||
{ name = "rpds-py" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema-specifications"
|
||||
version = "2025.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "referencing" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning"
|
||||
version = "2.6.6"
|
||||
@@ -1594,6 +1621,44 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "httpx2" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "mcp-types" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyjwt", extra = ["crypto"] },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||
{ name = "sse-starlette" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/8d/e0d339616f4810e9051d4aba6887afab289ab1f81875fe908b606cdfd0e3/mcp-2.3.0.tar.gz", hash = "sha256:8b147a50441cf059dc88c684e0aeed3687f0aa0f39c6cde7b90330effd2b34d8", size = 4171955, upload-time = "2026-10-02T22:06:56.091Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d7/4a2878f128df729db3ca04e71bfce26405cc2e0c9ebe19c592feeb4c61b9/mcp-2.3.0-py3-none-any.whl", hash = "sha256:dd0c44c089d16453e8ae31a3877a0054d7a2314caaa81f5e0541b9b1734b2377", size = 368664, upload-time = "2026-10-02T22:06:52.119Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp-types"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/2d/7c251e34207f6c51000312fc8839111ac45cfe02023f90b44e7f1051dd8e/mcp_types-2.3.0.tar.gz", hash = "sha256:d1e46549edb35ee19a94940fcee6d1addd7e589ab7ea92dda83f5d84781fc362", size = 65898, upload-time = "2026-10-02T22:06:57.863Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/48/32583066b155bb5d4507e7887dc88ee23118cc4de8e2a12870c49719e2a8/mcp_types-2.3.0-py3-none-any.whl", hash = "sha256:968efdbdaedfab06adae40d378a34395f1090c5921d4be3c9cde283aaf76d91d", size = 69108, upload-time = "2026-10-02T22:06:54.099Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
@@ -1898,7 +1963,7 @@ name = "nvidia-cublas"
|
||||
version = "13.1.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cuda-nvrtc" },
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
|
||||
@@ -1937,7 +2002,7 @@ name = "nvidia-cudnn-cu13"
|
||||
version = "9.24.0.43"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cublas" },
|
||||
{ name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/30/7c257e3d5cb4fecb147b93895c66e29c93f8e76d74b45bb418ff0587c4ec/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:a6812a554a1ff0413e9c52b84c26c050380649ab9615f9c16bded368ce9f421f", size = 650976863, upload-time = "2026-07-02T16:23:39.248Z" },
|
||||
@@ -1949,7 +2014,7 @@ name = "nvidia-cufft"
|
||||
version = "12.0.0.61"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
|
||||
@@ -1979,9 +2044,9 @@ name = "nvidia-cusolver"
|
||||
version = "12.0.4.66"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cublas" },
|
||||
{ name = "nvidia-cusparse" },
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
{ name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-cusparse", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
|
||||
@@ -1993,7 +2058,7 @@ name = "nvidia-cusparse"
|
||||
version = "12.6.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
|
||||
@@ -2167,6 +2232,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/88/763b967f7efd7226b82c9fae16d560cba049b1f0c036647e65c610fd636e/opencv_python_headless-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:829717b6a95554f273e49e357cee3b3a2a26b6f4842fbc1bed2b45bdd8f87e0e", size = 43825962, upload-time = "2026-07-02T05:50:09.627Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-api"
|
||||
version = "1.45.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opt-einsum"
|
||||
version = "3.3.0"
|
||||
@@ -2875,6 +2952,20 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.15.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/ea/5194e52748b0da83d71e082d75496eaec6e58f419f5e184786ded517e6a9/pyjwt-2.15.1.tar.gz", hash = "sha256:4f259e80cdfb6b3fc18a7de51fd1ef9ec79652f25019bae68975ca2468a34df8", size = 121252, upload-time = "2026-09-28T18:40:42.598Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ca/44de4e75f8aadc457f0634be3b542815078ded46dca30efb960edeecad6e/pyjwt-2.15.1-py3-none-any.whl", hash = "sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193", size = 33860, upload-time = "2026-09-28T18:40:41.429Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
crypto = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pypdfium2"
|
||||
version = "5.13.0"
|
||||
@@ -3071,6 +3162,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/60/d1/38f3a3405989a89ac18390803e70c6ad7c7760da4f9b83cbeca0c44a0c72/python_dotenv-1.2.4-py3-none-any.whl", hash = "sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc", size = 23266, upload-time = "2026-10-01T05:36:08.633Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.32"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytorch-lightning"
|
||||
version = "2.6.6"
|
||||
@@ -3102,7 +3202,7 @@ resolution-markers = [
|
||||
"(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "numpy", marker = "python_full_version < '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/75/50581633d199812205ea8cdd0f6d52f12a624886b74bf1486335b67f01ff/pywavelets-1.9.0.tar.gz", hash = "sha256:148d12203377772bea452a59211d98649c8ee4a05eff019a9021853a36babdc8", size = 3938340, upload-time = "2025-08-04T16:20:04.978Z" }
|
||||
wheels = [
|
||||
@@ -3178,7 +3278,7 @@ resolution-markers = [
|
||||
"(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "numpy", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/84/a9e80e33eb114e78bd2f1829a90d72d8d4d660109c721a8aa2f0d58d7592/pywavelets-1.10.0.tar.gz", hash = "sha256:f3cb8640225a8f3fdcbc3f6f3bd112c78927c9407cbfa65a5ff303990a6a3e32", size = 3948163, upload-time = "2026-09-07T17:15:15.637Z" }
|
||||
wheels = [
|
||||
@@ -3220,6 +3320,25 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/61/0375be8264b1b86b3182459bec94ac0d6ffba08c9b6a090840d4ba38d05b/pywavelets-1.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeb5073bb1859f7a911b275ab0fef1e41acede52fb1661331d74cb7f45ecbb4c", size = 4217922, upload-time = "2026-09-07T17:14:40.596Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "312"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/f5/10a6e845a00fc5e7afd0a988b744f403d4d57162a28d160a093c4d9322f0/pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c", size = 6362659, upload-time = "2026-06-04T07:49:21.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/c4/dcd2d62b5944b6d5db53413a5899016ccd57ffcb7278f3f81655d25d2027/pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a", size = 6928825, upload-time = "2026-06-04T07:49:23.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/56/3cbb433fe4501cdba2eb9040f56a4e1a8243faa4186b25295564d1a7a79d/pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47", size = 6721875, upload-time = "2026-06-04T07:49:26.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
@@ -3255,6 +3374,20 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.37.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "rpds-py" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2026.9.29"
|
||||
@@ -3405,6 +3538,7 @@ dev = [
|
||||
{ name = "httpx" },
|
||||
{ name = "huggingface-hub" },
|
||||
{ name = "invisible-watermark" },
|
||||
{ name = "mcp" },
|
||||
{ name = "numpy" },
|
||||
{ name = "openai" },
|
||||
{ name = "opencv-python-headless" },
|
||||
@@ -3515,6 +3649,7 @@ requires-dist = [
|
||||
{ name = "huggingface-hub", marker = "extra == 'source-classify'", specifier = ">=0.20.0,<2" },
|
||||
{ name = "huggingface-hub", marker = "extra == 'text-draft'", specifier = ">=0.20.0,<2" },
|
||||
{ name = "invisible-watermark", marker = "extra == 'dev'", specifier = ">=0.2.0" },
|
||||
{ name = "mcp", marker = "extra == 'dev'", specifier = ">=2.3,<3" },
|
||||
{ name = "numpy", marker = "python_full_version >= '3.13' and extra == 'pixels'", specifier = ">=2" },
|
||||
{ name = "numpy", marker = "python_full_version < '3.13' and extra == 'pixels'", specifier = ">=1.24.0" },
|
||||
{ name = "onnxruntime", marker = "extra == 'classify-onnx'", specifier = ">=1.24.0" },
|
||||
@@ -3591,6 +3726,112 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpds-py"
|
||||
version = "2026.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/68/3bd46b8a5e01d3c2ebdf9c5e9497912e3fe0cde02bac21a7130ca866e403/rpds_py-2026.9.1.tar.gz", hash = "sha256:4793ef7f78268b124b73fa933440f01d258bbae01de9fa53e9080c9ab0425a12", size = 63948, upload-time = "2026-10-04T16:32:36.469Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/6d/ddb8dfcf41892ba9c672d27988592c038d30db61708230313831b6780a1b/rpds_py-2026.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2711d29b653b3bce48a63d18b9c6b53274669e6d6c4094dddeb4d9a0e45128b2", size = 344947, upload-time = "2026-10-04T16:28:55.207Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/b2/632c3d034961149eff606e3e357547620a9ab37b849ae5815f67838753fd/rpds_py-2026.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3231c4c0e521dafa5be0c9f114ee2c2ad46650836f2d72caa86801950c3e7044", size = 346770, upload-time = "2026-10-04T16:28:56.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/45/4df346410bb873d03ec60c3b891d1fceb8cabde1971d29844bbb625cc296/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e01b3c878c8641913e688edd1b3f08658c6783d29cf6b826bd3c0d1ae7a1ffaa", size = 377795, upload-time = "2026-10-04T16:28:58.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/b8/a70f302207e4f29f873c01a986fcac5def49b9c8224328cef1ad7da6b7a9/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e524c7874ac72884d28e16dd5b8d839fd09e0fe76b020d3fbca23212a7b8c52", size = 383137, upload-time = "2026-10-04T16:28:59.411Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/e3/3aa28a48a01169d0e20728bf0e5ebbea3ae2774285b40853ae49b3ef332a/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761fdae6728ceb99ab182fad2f0cc1e262f610834dc891aea1d1a2a2e634776f", size = 496789, upload-time = "2026-10-04T16:29:01.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/a7/414e8d088602d66ad6f47c65bf07fe5090992a8574cdd4571c1a83b647ed/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b723eb406dec5bc9ec516c73ab9c3239a3284e017f7eb89ee2b3258bd504fb7", size = 395929, upload-time = "2026-10-04T16:29:02.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/5c/aac3b960fd55d37b49f77c0eebbe043eaf36762ebd48e7b46951e59c1c05/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136a1c3fe4402b7008bc81cb62ee538481795b61a7e83df88dff3b3f02b726ff", size = 375426, upload-time = "2026-10-04T16:29:03.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/80/77987a41c40be7202a78d9aed4a650bfb05792237cfd3b9e22638d990fe5/rpds_py-2026.9.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:839dde845559254f34885267c6878f60d61d5205180226d976fe488d45fa128e", size = 381497, upload-time = "2026-10-04T16:29:05.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/d0/640b02eb1ecd71a913c1178b6235287b31006266bca9a26862e80e497b54/rpds_py-2026.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56cd8b3f77d7b6812f533b662186a1f28316931166ddc00fb893b1b0db7e9888", size = 405740, upload-time = "2026-10-04T16:29:06.578Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/8c/d034195d45d215625a23f2ae85dbbdb86a49a93a51272061c97c80b9d316/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:074a4d198bc34d9a8ea425114fc3ded6d11ec01f6a314a8db67454a5152d8834", size = 555850, upload-time = "2026-10-04T16:29:08.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/f8/532f1addfc86207bc3cf23749638bb0d457bce891595012ed8a08d0543b8/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6beb738155fe8ab8091afdfa5a3226b21c2b1593f1e50ebb90eb25b44dbc0391", size = 618661, upload-time = "2026-10-04T16:29:09.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/5b/b0f2c9833a36a5b1f6ac5cb0e635fabacf713266f46ceefba2102da0bf51/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57492a550a1d88d29d003247e5f78dd8cf04a701fac0e4c8db8745a6d2504e0a", size = 583530, upload-time = "2026-10-04T16:29:11.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/20/464ed4b98d3c8c6a334f3b5e24d3886de04b05658a95fc40a37e0e6e4775/rpds_py-2026.9.1-cp311-cp311-win32.whl", hash = "sha256:d95a354e02393eada6d7351184671aced9d4cce109dabf927cb7aa99624352a1", size = 208125, upload-time = "2026-10-04T16:29:12.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/c9/384a9b62f8cd89c8e3e609b8c9052c140e8c01d807261e0517c904b8bf29/rpds_py-2026.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:ce4d4f52e2a4324396caddbd45a97d8d7be5f42edd25d2355282a9c34f9b2f7f", size = 226045, upload-time = "2026-10-04T16:29:13.751Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/3b/0a691f0dcb2301fa9c736346da2d0d4991778373283301c5c12b8f952f3d/rpds_py-2026.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:fdcd198979b4ecffcc1beba366a7fbcf4eb41243691a82fe52ceb0b902f09c12", size = 222636, upload-time = "2026-10-04T16:29:14.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/34/a828586ea3329fbb50895b50e9cf98ca3d924a9f41a0b26d443bff1b3794/rpds_py-2026.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:50906f5aea24b5a865cbd0a589698288631d9f3a54c3a937c83aefa95a0d14af", size = 346822, upload-time = "2026-10-04T16:29:16.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/81/ac6a0d064982251856ce009c9e1dd51a34110b3c055aa7d1aad18b4899a3/rpds_py-2026.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e21c1429e205828ea886a2293a4a2c8e01f4c25d9893ca330e97a6cf73f52e7b", size = 341380, upload-time = "2026-10-04T16:29:17.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/ff/bf7d54f362748fd6a49277b9d6531c394074110fedf50ad791ec59133fbb/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693b2728bbcc48d09a981a356954b0c47c53ff25b545856f28a889ea619f69a", size = 373053, upload-time = "2026-10-04T16:29:19.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/de/74b0ccdbbd28687b9b5fdb34c1cabed88352182facaced9d6f5486b8b9ed/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8601470267d938bcb7f3ab1a336100af51a4fd5b6ed030ef52461bb3ef5e7e07", size = 377742, upload-time = "2026-10-04T16:29:20.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/6d/b979775a3057b2a5c26d75ecbff60a20a24ef081db6dd76836fca3f20247/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3890a6aa36e6baa53d5258a2a25d3ef8b37ad165a6ab27a892d7c3e3a432cd69", size = 487289, upload-time = "2026-10-04T16:29:21.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/5d/7c34734ce3ece943d9d6ee0122a74a21bbc75b47acb5b7053c83f6efb1ed/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b5b393eda5ea42cca1c1a6665f2a4882b4fd5d1777e41ce0545a107fb008c9d", size = 390710, upload-time = "2026-10-04T16:29:23.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/6d/b26eb1e75395925b3a142ed351cbed2ec8212f5c9d937aee3df32c701baa/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:addeda51556dac7c1a2f14cda62db8b621cd12afba3091d03a96c72932387eab", size = 371261, upload-time = "2026-10-04T16:29:25.1Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/98/b2fdfe10301a9e27af21e634337fbba7baac0bc17fe44619a17c26bba2cb/rpds_py-2026.9.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d9edf30457d74eebfd76b045535e36f1cd89062566a128a0db2145ca042d787e", size = 376075, upload-time = "2026-10-04T16:29:26.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/b1/c4b8d954e49c3c69cf8063c0c9e0919c99f3d2cecc46311606a1cbe835dc/rpds_py-2026.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:815d26356930846a40c7bc1366e7b1b0320ab8a063e66c11298a208bed0fd237", size = 399358, upload-time = "2026-10-04T16:29:28.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/59/9559a7293c97dff0cbb293efffbd36644103883ecc27741af8ef90c84ca8/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b5a6f40f0a1486b4b36c888123afc67acdbd9f33235927acf5ff295429a0ba3", size = 550267, upload-time = "2026-10-04T16:29:29.851Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/9d/6dc60e49511de4c8b00e74f9c1d43aed4d27f712cdb1ade92f2c199cbf64/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5b8b0753718d258fd454283fbd57e14545d3b40583fa672e27cb4f987626bcc", size = 614018, upload-time = "2026-10-04T16:29:31.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/bc/92a4ecf27301b886232a413360f6b348a81d55ffef654f2ebda5970ebc3d/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:46d80bc76b51a6c24f9944368c28d38b8bcbcea1da4f2f8d3ebc31a67e8c6ec6", size = 578509, upload-time = "2026-10-04T16:29:33.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/53/efb97f2e6589b7ab8394385a73870e0b2ab2a195281c117b21fd6d7d4855/rpds_py-2026.9.1-cp312-cp312-win32.whl", hash = "sha256:befc2d6a953e563f8a7bfd87a42c22ebf8a3e980dcb7b6a4d17b70b0e914e8a3", size = 205401, upload-time = "2026-10-04T16:29:34.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/84/1700cc748d0eaa747486e4e82882d2575224e6e9a3148df583058850c629/rpds_py-2026.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ce8943f79c2210f7abcc28e86367b03b28d95027fd01c46d2472373ae70c86f", size = 222923, upload-time = "2026-10-04T16:29:35.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/89/0302215373c2f8b4408b0fdfee955368cc378a98ff59508d47a7e0dc2dbf/rpds_py-2026.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:501909f2e4a1e2dee528ef766fe3c469060ebc17e54a8383d404ba07a81a6f02", size = 218415, upload-time = "2026-10-04T16:29:37.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/ea/ee88fd9e756ff93fb6b1182a47ec09504a242620e33ce1d20679efefe841/rpds_py-2026.9.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a36b70596407634ca82d4b989a3729074a008537a0522e4c8046a67c729103e9", size = 346229, upload-time = "2026-10-04T16:29:38.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/71/a097d6552f837500fc36e6b23d09cfb9890c3cc47531f9ca64e149799615/rpds_py-2026.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eba5d173f7d5708b22a93815017a4611873ed54db9f268077c0dd1ed99cfc858", size = 340514, upload-time = "2026-10-04T16:29:40.405Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/b7/497e85768bf4e0d8ddbaa096a4cac31d1509251dee2728a8490aa367e0b5/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:457866b85daf5034296666168b84a69e0b2e89dc4f1af102b46f6448a60b9063", size = 372611, upload-time = "2026-10-04T16:29:41.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/4b/74ab4108916250b6e198e0d3af05bc6835eb046315f22f7a0ceb49667c5a/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a3a52a3ba86436ab3aef510fbe21512abc2ddd1993005dfe50514bd2284ef025", size = 377712, upload-time = "2026-10-04T16:29:43.242Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/8e/067e77d9d7b3cc793c9d909b7e97e7aadbbb1fb094093b6876902cc96d38/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7841166b7fa64c9c56404617ae4341448847482d45933b13135d26c130519e5", size = 488106, upload-time = "2026-10-04T16:29:44.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/b4/c5aae6c2dde269bf955f6b7d35065c655a57e47750d9668052ad67e74dda/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:926bdd3e3b5998ddf70cc64bc8cf57209571f9044542913afb673799fec77dd0", size = 390795, upload-time = "2026-10-04T16:29:46.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/36/76fab39973ee11e7f9f357c55138197bb01c86f6502cb76487e3b4f42db0/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7868b85224291c6cb6759f9b5adb9745f486d226f62b16a614dd5a2a5ab2b35b", size = 370794, upload-time = "2026-10-04T16:29:47.603Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/fe/cd2a80e6d7b871937a60e935c5d507aa390d143f4ff3636f640b9733d5df/rpds_py-2026.9.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3cd182d7291d29b92c521a0069d9c01ba6193628a9a105531d11b40a6d731a33", size = 375673, upload-time = "2026-10-04T16:29:49.223Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/18/7464a9953724e55a3b3206062fa0ffdeaa519584c6aabf65d3956d94f131/rpds_py-2026.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6ea1cda8d8c688278430e4268a42f5e5da3bdd74578dfadc0820c3f1766ce83", size = 398758, upload-time = "2026-10-04T16:29:50.601Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/86/1534b436700fd49ff411063b7c4d7e938adfabf90895b6cf1622d5a7d1f6/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5943980471829f6de242a20b109de3111ba6b77e3af0ffc587028ac854b05e6c", size = 550370, upload-time = "2026-10-04T16:29:52.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/1c/e1fa82a8a01e3c5820f3ba98a8b2673f642128eb368fa88871b01dd2c909/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:76d3af9732d2dab69f28179b40ba2d87e2f1d5824b4a694780aa787d685e8f36", size = 613106, upload-time = "2026-10-04T16:29:53.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/55/b20b8c4c3dde8755bfcd5b08492a02d0cd2e26929aedf6199ca2a377d42a/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:78326f4cb4427a56ba4996c0762b63be45f06b85f086526420d2b3a66e40f84d", size = 578235, upload-time = "2026-10-04T16:29:55.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/42/df3f7bbc3f7ab37a8a9db8d6c2ff2c985422899f1f7926afbf7ec3c0b8b4/rpds_py-2026.9.1-cp313-cp313-win32.whl", hash = "sha256:172e47169583f46ce118cbec68e6795d0da0f4606b488b6434f8276bca0a058c", size = 205293, upload-time = "2026-10-04T16:29:56.669Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/9c/ba5a9569d719bfdd6ce863df4133ac6a1658cf1b07cc3534c31db729fbbc/rpds_py-2026.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:3e93b2cd69a9830be33e03945cd7cda940a0a8bfcfbff41d6144f0cb0d3d8bd9", size = 222409, upload-time = "2026-10-04T16:29:58.049Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/72/f28ca566f6c23c35bbf7445f65eb0364577b25a026305995e24f78b83d94/rpds_py-2026.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:d151e148117294133bf8af7eeace085e7e87432db15ab6adf640330298a47f6f", size = 217681, upload-time = "2026-10-04T16:29:59.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/f2/67b94be1532767803415c1c5a1fd88ea487643d74a673cec1ba140af77bb/rpds_py-2026.9.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c9d1aca01f49170fdcf5c92761b1fafe97f554b721ca4570c5949fff778f0d4b", size = 347178, upload-time = "2026-10-04T16:30:00.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/37/b751de2b59b0197a1d92a5dd491de88e8a5e928c2e6562581974f1e85263/rpds_py-2026.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3f0e9ac28fc067d4d34b88ae43c48e9489455c97fee9633d851f7eeed5a05d35", size = 341878, upload-time = "2026-10-04T16:30:02.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/e2/5873bc4643c250db9e05d48dc0c93763d4aa68bc3b81164cb1af3b45b284/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07deecbfce94c78473018bc7d10b337cc651d12df87a1eb2cb3e4024bc9c33d0", size = 373963, upload-time = "2026-10-04T16:30:04.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/03/5acf7632158247f3f6386ff0af3a1ee48167d575037e8d0920594b76d92b/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:821b2755db9194409254012f429c56643416fb96ef9be090be82ec8826b7f477", size = 377975, upload-time = "2026-10-04T16:30:05.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/00/63fda451b8bffa5808fc8bb311ee7c073b340974b09f273c7b2a145d3d62/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c91c210ae7645626c608400e3519b4a642f837cce09ca830db3beb2e9f274d4", size = 490347, upload-time = "2026-10-04T16:30:07.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ae/c093ffd070ba0fb02f76c565d06fecc65ad6e4afdbae78f7031076d3cdac/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54ac2158a6f96cfbabff0b2eedaf94b90c5ec7ca8317fcadc61e1c2b2e0ff6ef", size = 392962, upload-time = "2026-10-04T16:30:08.77Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/9d/d08a1128ab199b2f0cf25bfeb0639bd05119fff4b7c47bec24ef9a8ec23f/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac2f5dbafd585dfe31f86a23ebf0d3ba480a9d49ebc87947267b5608d4ea0cd", size = 371623, upload-time = "2026-10-04T16:30:10.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/c7/4758ddcbb75609414bbccfcb11d612436f9b3ee821bd2f33f0f1604ee648/rpds_py-2026.9.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:8aa5dda18d39b6143eb24809d158f9252c88f402749b6f1b62a506cc7d96cc35", size = 376997, upload-time = "2026-10-04T16:30:12.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/e4/947bd7f608ff60faf46dc9d389c3dffd0e3d767d78a0be19978448ef0ce7/rpds_py-2026.9.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c90e7fa02e8f5de0d10c17595c568ada48c5302e749462c0ea1a4c362111a86", size = 400169, upload-time = "2026-10-04T16:30:13.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/35/fe93e020a0543b5670472c18d7e6af3197c08da571240ce1965c84f85c0f/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6d198bad4e49dd6732fbd636e2fc5c082f45c8cad0b4acb756b00c82c76072e", size = 551615, upload-time = "2026-10-04T16:30:15.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/4f/5d2a0136bb03b2a56a39dc6ff92d58a6e3e53a2e17079238b86228882f16/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:96beca19ec79de272e8668585380ff9092c47077c1d7a1e098e00bbd921f4785", size = 614655, upload-time = "2026-10-04T16:30:16.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/1c/3f1025aaf70d9bf7272cc41f8b64ee76b48bf01726248138430e16f23b38/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5cf77eb04f20b720be95265a3e00eb2a14814074255cc27069c551b2db53118", size = 579602, upload-time = "2026-10-04T16:30:18.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/0d/5c72e6204f76610608706da32b6b7e11ef7e10317558a7bb15a122e008dc/rpds_py-2026.9.1-cp314-cp314-win32.whl", hash = "sha256:a03d57b86d2a51d0a66c92177e2be154ad015f357791d306e714569999cdb4cc", size = 206315, upload-time = "2026-10-04T16:30:20.05Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/0b/489d48abbcc7d70cf3fbf662d9d22abf1f4650761c0a9ae05260800800d3/rpds_py-2026.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:837c6b305e26fe0f75b15c92cf3b2ba29e0ae19dc40b1c557b026cb426347d0c", size = 222852, upload-time = "2026-10-04T16:30:21.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/16/bbb05a7e6a10cf79ba639be7f799d770ee15f64175cc61d081b218dd402a/rpds_py-2026.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:fce4b85234a0cbad67bf8e6e1201ee815d172c9aebad75f25645bc4d834f8e31", size = 218709, upload-time = "2026-10-04T16:30:23.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/ac/ac507a0a4ec478ca470440a09583db4be5259ba7670aeba0620822f1e57a/rpds_py-2026.9.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a72c11530d71abfb66c8d7696a2f86c43e63fca8b948f1a784ac490f4ec688e", size = 349596, upload-time = "2026-10-04T16:30:24.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/f2/817a46b658d5070f477f722c298ee9a24525b0e4017347964146ef5fdd0e/rpds_py-2026.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:068c37bba854ec2fe42f7365c640af11dd9895890ccbf2df5070d0c059bd7f96", size = 343114, upload-time = "2026-10-04T16:30:26.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/42/6ade976b13ac1b4cb3bf2eb603f1be2fe74df19a29988d4c2b386be59d6f/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fca4eb6df565e2a928f1c7dad92d27db8f9df0f449e76423ed5d7e713ed445", size = 374197, upload-time = "2026-10-04T16:30:27.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/70/77cdf1d3f1a07faabe936016ae623aec7981f73108a8fe7a203ed2e21998/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c933c6678c6f116ff8af47a4c6db0868b8ace74af0343016c0ef00f00272ea69", size = 378025, upload-time = "2026-10-04T16:30:29.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/6b/18a44a3beaa9b7931acb04af7bd9539836477c630a794452d4826d6185d4/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:028ad274ea951dac64491b5d1e65712a4aeabfdbdb9fccf797b57bd899b0c495", size = 491969, upload-time = "2026-10-04T16:30:30.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/27/fb39cfd6bddaf741b024f813890374578ff8ac1f1adc473659c048b03b05/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d0a99cf9de0b17a3943388e9294a59becf75e7c43421f387bd3c7a9901f7c", size = 393905, upload-time = "2026-10-04T16:30:32.628Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/71/0fa7bb77b57af0d710273964180d11b503f62b8a5c358eb2d8c3f62feff6/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0da298fb372dc192610a4b9ecbc68a0cd8b675bbbd1fc519d01b41cfd658333e", size = 374569, upload-time = "2026-10-04T16:30:34.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/22/f41cfac269af3b449513ef1bc3d7f32fde52abbbd2d01c7e76b47743acd9/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eb61be926bb81567c1f48bdc8aa22b9855048dc2efd53871f9f7e6e9a5632346", size = 377482, upload-time = "2026-10-04T16:30:35.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/fc/312b49006e7f8f9ca5f96647577b8aa6f3df30519c46bd448f5c425af0b2/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42e75466f83cd43f6026c81eab74246efb2bdadafb307b85700632d06c68f299", size = 400040, upload-time = "2026-10-04T16:30:37.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/a6/18cca7a878dc7fa95165a83343fd4d7b65643fd22e54e47340a451121d5c/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:617f59cde379b4f648a09797b7f683d04b90a46344cddab85639da5aff0f5531", size = 551849, upload-time = "2026-10-04T16:30:39.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/6d/1f5685e20f39604691bdc3c05aaa6b8bd2f954e9e996477adf2376768e33/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3edae8c5ddfdb6985d49ae9d150516e5076888879022f91a26c2de9276ce0bdb", size = 614842, upload-time = "2026-10-04T16:30:41.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/25/98652109fd9f7e10268dd4571aa52b81987001b806f37ef1a18260de714a/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0f045bb053c9057720d72c56dffe30dffdc05997b2897a827b9325f0ab6623fa", size = 582606, upload-time = "2026-10-04T16:30:43.345Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/03/11ca09099bab5f53373a80a334c424ec917c13d050760a59499d2af5171e/rpds_py-2026.9.1-cp314-cp314t-win32.whl", hash = "sha256:bf35d0568abda97233239ce32896d3ad53fccc537832c104e30c94aa5fb93569", size = 203341, upload-time = "2026-10-04T16:30:44.954Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/8a/88909e3ffd9f47f5b58211473875d8c3c09079f0058c46fb72c55a702a26/rpds_py-2026.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:1e8d4d79d828299bf44a55db22a9388ab967b49d17132c88eab0f4360b48da8e", size = 222843, upload-time = "2026-10-04T16:30:46.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/ad/f0eba548e297987964ef8088403db875b333963bf8189f3f563b6936b20e/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c0d2cb595a420b34d5086db0add011e26e2c09d6a024afbac4228bf8f863a30", size = 384112, upload-time = "2026-10-04T16:31:42.643Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/89/891fa4d81f1eae7fe262ecd0aba2c51de94a116c7f3ccfb034fa75f4c537/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3d6ed6a98cfd19155996605474982cc470d7601746a6439078f1a5a3fa8b050", size = 389662, upload-time = "2026-10-04T16:31:44.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/46/912cf623b2263f9c9c2d04e8508fc38dbe89983faeba5fb36ca4558ae550/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8171b44a054e5c67fd748ada04187f1250bf35b95f85e52ab64bcf3331a923bb", size = 504637, upload-time = "2026-10-04T16:31:46.667Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/5c/7f3cfa7c5f01765c9f47c8398210ed0c1eb686c0e99c223cb1c6776d3fb9/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fda1d96e542c37b6c804547dbf489c129fe7c97183a76a5ec275909ba1a063df", size = 402607, upload-time = "2026-10-04T16:31:49.068Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/76/2e915d4115ee453c33e6af17bc1df7e22ddc52974e178eeeb92a78c09983/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270bdcdaac5d5b6f73c5e22e7e135c7f2a50e789f71d9e241d5be8d90026e19a", size = 382762, upload-time = "2026-10-04T16:31:51.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/e5/844382925d7b84a4074ad79fc2b9d05eb6231ea243b34f247e68e519b606/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:84a6ecc0c940169190d2c23bd969debd48c94dbc855acd60188a68d71d421608", size = 389563, upload-time = "2026-10-04T16:31:53.549Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/82/d6e3951602dc2e24790ef9d299aa24bf5e2493df5043f2c6da00480cc447/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab4b2fda7c2b542f7f9d886cc6a838c5079d2b76f72e6081411faba11adde2c9", size = 416842, upload-time = "2026-10-04T16:31:55.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/1c/8d248e4ea671d525ab7daa0703ad81cc2175076fa0ccd7ef088facbd2b01/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:44b32a7c4f0da3d28af31c259e38ddcff096f855e205ed0671d02fcf44f1ea1c", size = 562434, upload-time = "2026-10-04T16:31:57.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/38/e7ac0db898120d15dea25e3661b1242a40cb3b23d1f4db86c15a01fe0ba1/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:a3dbc5ed9514908d5046107d7b1346bde71eea61de6e0e4919c19354f97e769f", size = 627909, upload-time = "2026-10-04T16:31:59.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/c8/37e555514932cb1ee36c75349187aa882a98dee5d5654c761699ed8a0d27/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6eae33003518fd4cb4f83a218d5371469dd3001aa3b87128c005b07762f7fe5e", size = 593658, upload-time = "2026-10-04T16:32:02.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/5c/5f69a91add94a8a33acea68a2b034a464946a654ff7296af9d957ef0d6a5/rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_10_12_x86_64.whl", hash = "sha256:0483515261947e4e8b8e1375bf7463e7eb6ccfb3d86e7b554d90cd5285f20f32", size = 351769, upload-time = "2026-10-04T16:32:04.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/22/35ea8bf3b2682b90ab687c74e4828418ff12298a5b0b0a0c033f61c86a30/rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:4cfaf02209061880210819934de2f4f6aa83dc04dafe6770276acc240a56da31", size = 351719, upload-time = "2026-10-04T16:32:06.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/90/a9ba81408369d34c1aca73319c15adc91ed45c1d21e9e8ee4832ffb7fe0b/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdc537c8633d7fd92a82e2e0d2ab74320a3f63d5e59fb9cf08711e08fe151c4", size = 381109, upload-time = "2026-10-04T16:32:08.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/44/5192a0bed94cec86bd2a5e1cc5a1bb8f7eec3aec907eacd2deb13e87e326/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10e208f2425d973938afcd56e28a7c4be32e27b6a60b5d381f49fb9d8acf9759", size = 388222, upload-time = "2026-10-04T16:32:11.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/cd/5356549711448f18b52f7a11ffbe90f1228774996fbf3c6cd1b18d22abaf/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c0dbbcc19735fe5f8b0a54c07659d154a9e69f47e15d0a6ab7299215daf62cb", size = 500384, upload-time = "2026-10-04T16:32:13.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/88/ddda9d28adfe33119c75b2e733d4ba7e326f41d7e1b1093951771768ca48/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684fd492fff4fead00587544e059be2bbcb6f93454f21fa2a91b66fc7508be82", size = 401661, upload-time = "2026-10-04T16:32:15.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/c3/bb59ba16a6b4a57995d15b8c04c00f28df3b938c4dcdde48fbe0f73edb0c/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1028417bb44037eb3069c1009bd7b7277212876cda22fbe565b0bca9fab6d2c", size = 380748, upload-time = "2026-10-04T16:32:17.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/b8/0580faa1c5a32ddc160130dd7ae54d2d007272a89a40a6b5db3e50f9f585/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:492e5e428cbe126221611f47e068f01660352feec4ad18bc0f5ea9b2ae88fb14", size = 385731, upload-time = "2026-10-04T16:32:19.855Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/70/f73564642bbe3322c7eeef2c2f258cf040b71c41a430b532e59d04a1b838/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:88b5268892fde430d5531f95bc560b6efbbd67c929662c586afd729a96e7461c", size = 412131, upload-time = "2026-10-04T16:32:22.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/5d/17fff2e1f8f68721a2afb5cb48f7e442c751bb6b4883157c5abaf618cd08/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:01445c8d194aa032a08e944f16567672da1c62dbdbefd8b6d0693032e290cf68", size = 558193, upload-time = "2026-10-04T16:32:24.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/d0/869ab6fb08531f97aae4a780b4d61b4190d8aef48fe054ee28bcf7114466/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_i686.whl", hash = "sha256:eef6a03b0b6d08d0835ccfa8ec8d1bc70525e3801387567137b50c557695e6da", size = 622448, upload-time = "2026-10-04T16:32:26.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/a5/9672e532fe02cd3b92c78cfd8743abca939d96853006c63835009a1ecb6f/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6b9bf3135b4ad5981df9a73d71a35272d650a2985ae9c2746357b24d59de2448", size = 591126, upload-time = "2026-10-04T16:32:29.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/ef/3a9f8e4279c7920af561deed4cb7ebebed2794a8f608cf404d5eef14a105/rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_10_12_x86_64.whl", hash = "sha256:56c6952a9b15047466d0c2347c446a761d4527f89976156341e68f0ce5cc08b0", size = 357108, upload-time = "2026-10-04T16:32:31.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/55/4b2fa381a583760aea5c92841e4e928a358e1c6511b129219e9c2826a226/rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_11_0_arm64.whl", hash = "sha256:b242c27c8f836305a4a72df9cdd564386ac57b807bd252a063223331c9316b37", size = 346877, upload-time = "2026-10-04T16:32:34.061Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruamel-yaml"
|
||||
version = "0.19.1"
|
||||
@@ -3794,6 +4035,32 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "3.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/be/0123026f719d1a7936f214a88b553bb5701e04ff2511147c1dab0c5035eb/sse_starlette-3.5.0.tar.gz", hash = "sha256:75de713aa8a9441513cc283220826da079d982770965b951e9437720e8bafdb2", size = 36794, upload-time = "2026-09-28T17:48:14.7Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/e4/cdda14023c316d71493bc54fdffc3dd006631b88866145c9d3cc33e0f1df/sse_starlette-3.5.0-py3-none-any.whl", hash = "sha256:3e6e1070df3f0f5d9cea81496de92dbb72f6721871d99748ece67441dd8b7997", size = 17407, upload-time = "2026-09-28T17:48:13.228Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "1.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/0c/6efb252d091ecccd7d62048ae11f0ea35cd75a4fbaeea5e30f9c3bf91d10/starlette-1.8.0.tar.gz", hash = "sha256:1565dc0b35d5737a271ed1e0e04e949f4e81198799f216d2667b0a0fb9cf9522", size = 2730457, upload-time = "2026-10-13T07:54:39.53Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/b0/5742e4ac7af5eb58ec3470a537a49d7aa507e5539413e504b3a65ef50ba8/starlette-1.8.0-py3-none-any.whl", hash = "sha256:dfdd6b29c26483288088d990eee59631dedadd66ce20d203402a7ca8e3c4656f", size = 79612, upload-time = "2026-10-13T07:54:38.019Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sympy"
|
||||
version = "1.14.0"
|
||||
@@ -4200,6 +4467,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.54.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "h11", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/da/34/30e9280707135d2cfc589dfff3cb796bd07a3aeb1a3e415ba09dd89d7bb4/uvicorn-0.54.0.tar.gz", hash = "sha256:a2e33cbfaa0306f8e6b0c13e0cb89d7d7a2da3e62b90c66e18c33d9807b28620", size = 112283, upload-time = "2026-09-25T06:52:37.601Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0c/b54a4fdd7f90a3af8b02ebc9ce6712c2c208b7926a2f7bad95c33ebbe943/uvicorn-0.54.0-py3-none-any.whl", hash = "sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf", size = 87427, upload-time = "2026-09-25T06:52:35.829Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.9.1"
|
||||
|
||||
Reference in new issue
Block a user