diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24c04af..cef48b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,34 +1,39 @@ -name: ci +name: Tests + on: push: + branches: [main] pull_request: + workflow_dispatch: + +permissions: + contents: read jobs: test: + name: Test runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 with: - go-version: "1.25" + fetch-depth: 0 - - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: "3.12" - - name: Go vet + test - run: | - go vet ./... 2>/dev/null || true - go test ./... 2>/dev/null || true + - name: Sync toolchain (ladybug, model2vec, mistune) + run: uv sync --frozen - - name: Python install (tool deps) + - name: Shell syntax check run: | - pip install --quiet numpy mistune 2>/dev/null || true + bash -n bin/db/psql-yq - - name: Python unit tests (offline) + - name: Python unit tests (offline, vendored tools) run: | - python -m unittest discover -s tools -t . 2>/dev/null || true + uv run python -m unittest discover -s tools -t . - name: facts/audit self (lexicon consistency, no network) run: | diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..40a07fe --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,32 @@ +name: Release Please + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + issues: write + actions: write + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + steps: + - name: Run release-please + uses: googleapis/release-please-action@v4 + id: release + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + token: ${{ secrets.RELEASE_PLEASE_TOKEN != '' && secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Tag release notes + if: ${{ steps.release.outputs.release_created == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.release.outputs.tag_name }} + run: echo "released $TAG from conventional commits between releases" \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..46b1b67 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.0" +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 7e5740b..2289ea1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,8 +34,9 @@ Read first: [PLAN](PLAN.md) → [docs](docs/). ``` PLAN.md decisions + execution + open questions docs/ published docs -skills/ in-project agent skills (this is the integration target) +skills/ in-project agent skills (vendored, no external links) bin/ self-describing tools bin/{subject}/{method} +tools/ vendored python libs behind bin/* (yamlout, websearch) var/ kb.lbug, caches (gitignored) .venv/ ladybug + model2vec + mistune ``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e798788 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Andriy Oblivantsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/PLAN.md b/PLAN.md index bb2b8d6..4f0c500 100644 --- a/PLAN.md +++ b/PLAN.md @@ -26,7 +26,7 @@ detective method: **a fact needs ≥2 independent sources or it is |---|----------|--------| | D1 | RAG corpus | ops stack (chat, onlyoffice, gitea/NPM, searchxng, observability, ai-bot, mcp-servers, `~/.ssh/config`) + portfolio. Exclude `office.dev` + jobs/applications. | | D2 | skill merging | integrate skills **in this project** `skills/`; skip gitea / brain-detective-depe ndent skills. | -| D3 | web search | import `web-search` (agent-skills), retire local `searxng-ops`. | +| D3 | web search | import `web-search`, retire local `searxng-ops`. Vendored here, no remote link. | | D4 | embeddings | **model2vec** `minishlab/potion-multilingual-128M` instead of embeddinggemma. | | D5 | parser | **mistune** for MD → leaf extraction (duckdb-md documented as future optional SQL/export layer, not v1). | | D6 | graph engine | **LadybugDB** (Kuzu successor, MIT, embedded, native FTS+vector+Cypher). Python binding for `bin/*`; Go shebang for golang tools. | @@ -56,8 +56,8 @@ detective method: **a fact needs ≥2 independent sources or it is kb/get kb/stats kb/eval md/import md/select md/tables md/gaps (mistune) brain/extract brain/audit brain/deduce (thinking wrapper) - web/search (symlink into agent-skills web-search) - db/psql-yq (symlink into agent-skills) + web/search (vendored) + db/psql-yq (vendored) ssh-tunnel onlyoffice pg tunnel 5433 var/kb.lbug single embedded store (gitignored) .venv/ ladybug + model2vec + mistune + numpy @@ -113,7 +113,7 @@ Feedback loop: every commit → PR → CI → green/gate → merge. Same discipl 1. scaffold repo (:done after this file + AGENTS.md + .gitignore + ci) 2. gh repo create eSlider/2dph --private + initial commit + CI -3. agent-skills clone + skill integration (web-search, db-yaml, kb-search, agent-cost, diataxis-docs) +3. vendored skill integration (web-search, db-yaml, kb-search, agent-cost, diataxis-docs) — no remote links 4. .venv: ladybug + model2vec + mistune 5. schema + tools with TDD (kb + md + facts + brain) 6. ~/.config/brain config diff --git a/README.md b/README.md new file mode 100644 index 0000000..f0c0a93 --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +# 2dph — deductionphile + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Python](https://img.shields.io/badge/Python-3.12+-3776AB.svg)](https://python.org) +[![uv](https://img.shields.io/badge/uv-managed-261230.svg)](https://docs.astral.sh/uv) +[![Tests](https://github.com/eSlider/2dph/actions/workflows/ci.yml/badge.svg)](https://github.com/eSlider/2dph/actions/workflows/ci.yml) +[![Latest Release](https://img.shields.io/github/v/tag/eSlider/2dph?sort=semver&label=release)](https://github.com/eSlider/2dph/releases) +[![GitHub Stars](https://img.shields.io/github/stars/eSlider/2dph?style=social)](https://github.com/eSlider/2dph/stargazers) + +An evidence-first brain over the operational eSlider stack. **Facts need two +independent sources, or they are `(not confirmed)`.** + +`2dph` is a single embedded knowledge graph (LadybugDB = Kuzu successor) with +native **HNSW vector** + **BM25 full-text** indexes, built from markdown, +compose files, ssh config, docker state, and git history. Search is +*deduction*: confirmed facts first, supporting info second, `web-search` as +the independent second source when the local graph cannot confirm. + +## Architecture + +```mermaid +graph TB + subgraph corpus["Corpus"] + OPS["ops stack
chat · onlyoffice · npm · observability · ai-bot"] + SSH["~/.ssh/config"] + CV["portfolio yaml"] + GH["git history · authors"] + end + + subgraph dph["2dph tools"] + EX["bin/facts/extract
2-source pairing"] + AU["bin/facts/audit
confidence + staleness"] + IDX["bin/kb/index
chunk + embed"] + MD["bin/md/import
mistune leaves"] + SR["bin/kb/search
deduction + --hop"] + end + + subgraph store["Ladybug var/kb.lbug"] + FACTS["facts root
confirmed"] + INFO["info root
narrative"] + VEC["HNSW cosine"] + FTS["BM25 FTS"] + GR["File→HAS_VERSION→Commit→AUTHORED→Person"] + end + + subgraph ai["AI"] + M2V["model2vec
potion-multilingual-128M"] + end + + subgraph ext["External"] + WS["web-search skill
(2nd independent source)"] + end + + OPS --> EX + SSH --> EX + CV --> EX + GH --> EX + EX --> FACTS + EX --> INFO + MD --> GR + MD --> INFO + IDX --> M2V + IDX --> VEC + IDX --> FTS + IDX --> FACTS + SR --> FACTS + SR --> INFO + SR --> VEC + SR --> FTS + SR --> WS + AU --> FACTS +``` + +## The method + +Every assertion is `Who / What / How / Where / When + evidence + confidence`, +mirroring the detective detective skill: **≥2 independent sources confirm a +fact; conflicting sources or a single source → `hypothesis` → `(not confirmed)`.** + +| root | meaning | used for answers | +|------|---------|------------------| +| `facts` | assertions backed by ≥2 sources (`confirmed`) | yes, with evidence links | +| `info` | descriptive/narrative leafs (how-tos, notes) | context only, marked `(not confirmed)` | + +## Deduction search + +```bash +bin/kb/search "Matrix federation over HTTPS" # facts → info → web-search +bin/kb/search "what runs on arc-2" --hop 1 # walk graph edges +bin/kb/search "where is cs-lexicon" --json | yq '.' # YAML by default +bin/kb/get --body # full chunk on demand +bin/kb/stats # index health +bin/kb/eval # recall@5 gate +``` + +## Storage + +- **LadybugDB** — single `var/kb.lbug`, Cypher property graph, HNSW + BM25 + in one engine, embedded (no server), ACID, read-only-safe for concurrent + readers. +- **model2vec** — `potion-multilingual-128M` static embeddings (256-dim), + CPU-fast, deterministic, no Ollama runtime dependency. +- facts and info split semantically by `root` column but written inside the + same transaction. + +## Tooling conventions + +`bin/{subject}/{method}` — self-describing: shebang on line 1, usage comment +from line 2. bash + python primary; golang via the Go shebang when a compiled +helper is right. YAML default output, `--json` for machines. Everything that +touches network/db is read-only, throttled, cached. Tests gate every commit. + +## Development + +```bash +uv venv .venv # Python 3.12, uv-managed +uv pip install -r requirements.lock.txt # pinned toolchain +bin/facts/audit self # lexicon consistency gate +go test ./... && python -m unittest discover -s tools -t . +``` + +Docker (optional, cached model + var volumes): + +```bash +docker compose run --rm brain index # (re)index corpus +docker compose run --rm brain search "query" # one-shot query +docker compose up brain-watch # auto re-index on change +``` + +## Related + +- [go-second-brain](https://github.com/eSlider/go-second-brain) — the earlier + Neo4j + Qdrant + Matrix RAG brain +- [agent-skills](https://github.com/eSlider/agent-skills) — upstream + skills (`web-search`, `db-yaml`, …) that 2dph integrates +- [detective](https://github.com/detective) — the two-source method + +See [PLAN.md](PLAN.md) for decisions, execution status, and v2 open questions. \ No newline at end of file diff --git a/bin/db/psql-yq b/bin/db/psql-yq deleted file mode 120000 index f625f1e..0000000 --- a/bin/db/psql-yq +++ /dev/null @@ -1 +0,0 @@ -/mnt/8TB/projects/ai/agent-skills/bin/db/psql-yq \ No newline at end of file diff --git a/bin/db/psql-yq b/bin/db/psql-yq new file mode 100755 index 0000000..be3fdcb --- /dev/null +++ b/bin/db/psql-yq @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# db/psql-yq - read any Postgres as YAML, cheaply and read-only. +# +# db/psql-yq --profile onlyoffice -t document_asset -l 20 # table sample +# db/psql-yq --profile onlyoffice -s document_asset # column list +# db/psql-yq --profile onlyoffice -c 'SELECT ...' # query -> YAML +# db/psql-yq --container my-pg --db app -c '...' # ad-hoc container +# db/psql-yq --dsn 'postgres://u@h:5432/db' -c '...' +# db/psql-yq -r 'SELECT ...' # raw rows, no YAML +# +# Profiles live in ~/.config/brain/db-profiles.yml so that credentials and +# hostnames stay out of every project repo. A profile is: +# +# onlyoffice: +# host: 127.0.0.1 +# port: 5433 +# user: onlyoffice +# db: onlyoffice +# password_env_file: /home/ano/.config/ops/onlyoffice.env +# +# The onlyoffice Postgres listens inside the QEMU VM (host port 32, SSH only). +# Credentials come from /etc/onlyoffice/documentserver/local.json inside the +# VM (dbUser/dbPass), read via db/ssh-tunnel, never committed. +# db/ssh-tunnel opens 127.0.0.1:5433 -> vm:5432 before querying. +# +# Read-only guard: any DML or DDL keyword is rejected, whatever the profile. +set -euo pipefail + +PROFILES="${BRAIN_DB_PROFILES:-$HOME/.config/brain/db-profiles.yml}" +IMAGE="${BRAIN_PSQL_IMAGE:-postgres:13}" +LIMIT="${PSQLYQ_LIMIT:-50}" +MODE=c +ARG="" +PROFILE="" +CONTAINER="" +DSN="" +DB="" +USER_NAME="" + +usage() { sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//'; } + +[[ $# -eq 0 ]] && { usage; exit 1; } + +while [[ $# -gt 0 ]]; do + case "$1" in + --profile) PROFILE="$2"; shift 2 ;; + --container) CONTAINER="$2"; shift 2 ;; + --dsn) DSN="$2"; shift 2 ;; + --db) DB="$2"; shift 2 ;; + --user) USER_NAME="$2"; shift 2 ;; + -c) MODE=c; ARG="$2"; shift 2 ;; + -t) MODE=t; ARG="$2"; shift 2 ;; + -s) MODE=s; ARG="$2"; shift 2 ;; + -r) MODE=r; ARG="$2"; shift 2 ;; + -l) LIMIT="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "unknown arg: $1" >&2; usage; exit 1 ;; + esac +done + +assert_readonly() { + local s="${1,,}" + if [[ "$s" =~ (^|[^a-z])(insert|update|delete|drop|truncate|alter|create|grant|revoke|vacuum|copy)[[:space:]] ]]; then + echo "db/psql-yq: read-only, query rejected" >&2 + exit 3 + fi +} + +prof() { yq -r ".\"$PROFILE\".$1 // \"\"" "$PROFILES" 2>/dev/null; } + +build_client() { + if [[ -n "$DSN" ]]; then + PGCLI=(docker run --rm "$IMAGE" psql "$DSN" -X -A -t) + return + fi + + if [[ -n "$CONTAINER" ]]; then + PGCLI=(docker exec "$CONTAINER" psql -U "${USER_NAME:-postgres}" -d "${DB:?--db required with --container}" -X -A -t) + return + fi + + [[ -n "$PROFILE" ]] || { echo "need --profile, --container or --dsn" >&2; exit 2; } + [[ -f "$PROFILES" ]] || { echo "no profiles file: $PROFILES" >&2; exit 2; } + [[ "$(yq -r "has(\"$PROFILE\")" "$PROFILES")" == "true" ]] \ + || { echo "unknown profile '$PROFILE' in $PROFILES" >&2; exit 2; } + + local container host port user db network envfile + container="$(prof container)"; host="$(prof host)"; port="$(prof port)" + user="$(prof user)"; db="$(prof db)"; network="$(prof network)" + envfile="$(prof password_env_file)" + + if [[ -n "$container" ]]; then + PGCLI=(docker exec "$container" psql -U "${user:-postgres}" -d "$db" -X -A -t) + else + if [[ -n "$envfile" ]]; then + [[ -f "$envfile" ]] || { echo "password_env_file missing: $envfile" >&2; exit 2; } + # shellcheck disable=SC1090 + set -a; . "$envfile"; set +a + fi + local net=() + [[ -n "$network" ]] && net=(--network "$network") + PGCLI=(docker run --rm "${net[@]}" -e PGPASSWORD="${PGPASSWORD:?password unset for profile $PROFILE}" \ + "$IMAGE" psql -h "$host" -p "${port:-5432}" -U "$user" -d "$db" -X -A -t) + fi +} + +build_client + +case "$MODE" in + r) assert_readonly "$ARG"; "${PGCLI[@]}" -c "$ARG" ;; + t) assert_readonly "SELECT * FROM $ARG" + "${PGCLI[@]}" -c "SELECT json_agg(row_to_json(t)) FROM (SELECT * FROM $ARG LIMIT $LIMIT) t" | yq -P . ;; + s) "${PGCLI[@]}" -c "SELECT column_name || ':' || data_type FROM information_schema.columns WHERE table_name='$ARG' ORDER BY ordinal_position" ;; + c) assert_readonly "$ARG" + "${PGCLI[@]}" -c "SELECT json_agg(row_to_json(t)) FROM ($ARG) t" | yq -P . ;; +esac diff --git a/bin/db/ssh-tunnel b/bin/db/ssh-tunnel new file mode 100755 index 0000000..34674f0 --- /dev/null +++ b/bin/db/ssh-tunnel @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# db/ssh-tunnel - open 127.0.0.1:5433 -> onlyoffice VM Postgres (host port 32). +# +# db/ssh-tunnel # background tunnel, ready for db/psql-yq +# db/ssh-tunnel --check # exit 0 if a tunnel is already up +# db/ssh-tunnel --stop # kill any tunnel owned by this tool +# +# OnlyOffice runs as a QEMU VM (docker network office_default), SSH on host +# port 32. Its Postgres listens on 127.0.0.1:5432 inside the VM and is not +# exposed. This forwards localhost:5433 to it so db/psql-yq can query with +# the onlyoffice profile. Credentials are never part of the tunnel. +set -euo pipefail + +SRC="${BRAIN_TUNNEL_LOCAL:-127.0.0.1:5433}" +DST="${BRAIN_TUNNEL_REMOTE:-127.0.0.1:5432}" +SSH_PORT="${BRAIN_TUNNEL_SSH_PORT:-32}" +SSH_USER="${BRAIN_TUNNEL_SSH_USER:-root}" +SSH_HOST="${BRAIN_TUNNEL_SSH_HOST:-127.0.0.1}" +MARKER="2dph-ssh-tunnel" + +case "${1:-}" in + --check) + ss -tln 2>/dev/null | grep -q "${SRC%:*}:${SRC#*:}" && exit 0 + pgrep -f "${MARKER}" >/dev/null && exit 0 + exit 1 + ;; + --stop) + pkill -f "${MARKER}" && echo "tunnel stopped" || echo "no tunnel running" + exit 0 + ;; + -h|--help) + sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + "") + [ -f "$HOME/.ssh/config" ] || { echo "db/ssh-tunnel: ~/.ssh/config missing" >&2; exit 1; } + if db/ssh-tunnel --check; then + echo "tunnel already up on ${SRC}" + exit 0 + fi + ssh -f -N -M -S "$HOME/.ssh/2dph-tunnel.sock" \ + -L "${SRC}:${DST}" -p "$SSH_PORT" "${SSH_USER}@${SSH_HOST}" \ + && echo "tunnel up on ${SRC} (-> vm:${DST})" + exit 0 + ;; + *) + echo "unknown arg: $1" >&2 + exit 1 + ;; +esac \ No newline at end of file diff --git a/bin/web/search b/bin/web/search deleted file mode 120000 index db76e68..0000000 --- a/bin/web/search +++ /dev/null @@ -1 +0,0 @@ -/mnt/8TB/projects/ai/agent-skills/bin/web/search \ No newline at end of file diff --git a/bin/web/search b/bin/web/search new file mode 100755 index 0000000..865ba15 --- /dev/null +++ b/bin/web/search @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""web/search - web search through the self-hosted SearXNG at search.ops.io. + + bin/web/search "LadybugDB vector search" + bin/web/search "model2vec multilingual" --site github.com + bin/web/search "uclancy" --category it -n 3 --json | jq -r '.results[].url' + bin/web/search "sqlite-vec" --refresh # ignore the cached answer + +This complements bin/kb/search: the knowledge base holds our own facts, this +reaches the public web. Use it as the second, independent source that the +detective method asks for. + +Exit codes: 0 results, 2 refused as possible PII, 3 throttled (not "nothing +found" - the instance answers 200 with an empty list when it throttles). +""" +from __future__ import annotations + +import argparse +import fcntl +import json +import os +import sys +import time +import urllib.parse +import urllib.request +from pathlib import Path + +TOOLS = Path(__file__).resolve().parents[1].parent / "tools" +sys.path.insert(0, str(TOOLS)) +sys.path.insert(0, str(TOOLS / "web-search")) + +import websearch as ws # noqa: E402 +from yamlout import to_yaml # noqa: E402 + +CONFIG = Path(os.environ.get("BRAIN_SEARCH_ENV", Path.home() / ".config/brain/search.env")) +CACHE = Path(os.environ.get("BRAIN_SEARCH_CACHE", Path.home() / ".cache/brain/web-search.sqlite")) +LOCK = CACHE.with_suffix(".lock") + + +def load_config() -> dict: + if not CONFIG.exists(): + sys.exit(f"no credentials at {CONFIG} (mode 600, BRAIN_SEARCH_URL/USER/PASS)") + conf = {} + for line in CONFIG.read_text().splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, value = line.partition("=") + conf[key.strip()] = value.strip().strip("\"'") + missing = {"BRAIN_SEARCH_URL", "BRAIN_SEARCH_USER", "BRAIN_SEARCH_PASS"} - conf.keys() + if missing: + sys.exit(f"{CONFIG} is missing {', '.join(sorted(missing))}") + return conf + + +def fetch(conf: dict, query: str, params: dict, timeout: int) -> dict: + args = {"q": query, "format": "json", **params} + url = f"{conf['BRAIN_SEARCH_URL'].rstrip('/')}/search?{urllib.parse.urlencode(args)}" + request = urllib.request.Request(url) + token = f"{conf['BRAIN_SEARCH_USER']}:{conf['BRAIN_SEARCH_PASS']}".encode() + import base64 + request.add_header("Authorization", "Basic " + base64.b64encode(token).decode()) + with urllib.request.urlopen(request, timeout=timeout) as response: + return json.loads(response.read().decode()) + + +def main() -> int: + parser = argparse.ArgumentParser(description="web search via SearXNG") + parser.add_argument("query") + parser.add_argument("-n", "--limit", type=int, default=ws.DEFAULT_LIMIT) + parser.add_argument("--site", help="restrict to one domain") + parser.add_argument("--lang", help="language code, e.g. de") + parser.add_argument("--fresh", choices=["day", "week", "month", "year"], + help="time range") + parser.add_argument("--category", help="SearXNG category, e.g. it, science, news") + parser.add_argument("--engines", help="comma separated engine list") + parser.add_argument("--json", action="store_true") + parser.add_argument("--refresh", action="store_true", help="bypass the cache") + parser.add_argument("--ttl", type=float, default=ws.CACHE_TTL) + parser.add_argument("--timeout", type=int, default=25) + parser.add_argument("--force", action="store_true", + help="send even if the query looks like PII") + args = parser.parse_args() + + query = f"site:{args.site} {args.query}" if args.site else args.query + + reason = ws.phi_reason(query) + if reason and not args.force: + print(f"refused: {reason}. This query would leave the host.", file=sys.stderr) + print("Rephrase without identifiers, or pass --force if it is genuinely public.", + file=sys.stderr) + return 2 + + params = {} + if args.lang: + params["language"] = args.lang + if args.fresh: + params["time_range"] = args.fresh + if args.category: + params["categories"] = args.category + if args.engines: + params["engines"] = args.engines + + key = ws.cache_key(query, params) + conn = ws.open_cache(CACHE) + + if not args.refresh: + cached = ws.cache_get(conn, key, ttl=args.ttl) + if cached is not None: + out = ws.project(cached, limit=args.limit) + out["cached"] = True + sys.stdout.write(json.dumps(out, indent=2, ensure_ascii=False) + "\n" + if args.json else to_yaml(out)) + return 0 + + conf = load_config() + LOCK.parent.mkdir(parents=True, exist_ok=True) + + # One request at a time across every agent on this host: the instance + # suspends engines for minutes when several of us ask at once. + with open(LOCK, "w") as lock: + fcntl.flock(lock, fcntl.LOCK_EX) + + payload = None + for attempt in range(1 + len(ws.RETRY_BACKOFF)): + delay = ws.wait_for(ws.last_call(conn), time.time()) + if delay: + time.sleep(delay) + ws.mark_call(conn) + try: + payload = fetch(conf, query, params, args.timeout) + except Exception as error: # noqa: BLE001 - report, do not crash + print(f"request failed: {error}", file=sys.stderr) + return 3 + if ws.classify(payload) == "ok": + break + if attempt < len(ws.RETRY_BACKOFF): + time.sleep(ws.RETRY_BACKOFF[attempt]) + + if ws.classify(payload) == "ok": + ws.cache_put(conn, key, payload) + + out = ws.project(payload, limit=args.limit) + sys.stdout.write(json.dumps(out, indent=2, ensure_ascii=False) + "\n" + if args.json else to_yaml(out)) + return 0 if out["status"] == "ok" else 3 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000..e5ff966 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1,14 @@ +# .dockerignore - keep the build context lean. +.venv +var +.git +.github +__pycache__ +*.pyc +*.lbug +*.lbug.* +.cache +.secrets +.skills-tmp +docker/.dockerclean +docs/.build \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..6dd5fca --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,30 @@ +# syntax=docker/dockerfile:1 +FROM python:3.12-slim AS base + +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=1 \ + HF_HOME=/home/2dph/.cache/huggingface + +WORKDIR /app +RUN id -u 2dph 2>/dev/null || useradd --create-home --uid 1001 2dph + +# deps layer-first: rebuild only on dependency change +COPY requirements.lock.txt /tmp/requirements.lock.txt +RUN python -m pip install --no-cache-dir -r /tmp/requirements.lock.txt \ + && rm /tmp/requirements.lock.txt + +COPY . . + +# wait for the actual tools (bin/*) to exist before wiring docker helpers +COPY docker/kb-watch /app/.dockerbin/kb-watch +COPY docker/serve /app/.dockerbin/serve +RUN chmod +x /app/.dockerbin/kb-watch /app/.dockerbin/serve \ + && chown -R 2dph:2dph /app +USER 2dph + +ENV PATH="/app/.dockerbin:${PATH}" +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD python -c "import model2vec, ladybug, mistune; print('ok')" || exit 1 + +ENTRYPOINT ["/app/docker-entrypoint.sh"] \ No newline at end of file diff --git a/docker/compose.yaml b/docker/compose.yaml new file mode 100644 index 0000000..6c112a9 --- /dev/null +++ b/docker/compose.yaml @@ -0,0 +1,63 @@ +# 2dph — best-practice docker composition +# +# docker compose run --rm brain index # rebuild graph +# docker compose run --rm brain search "Matrix fed" # one-shot query +# docker compose up brain-watch # auto re-index +# +# Caching: the 128M model (HF_HOME) and kb.lbug (VAR_DIR) live in named +# volumes, so rebuilds never redownload the model or re-derive the graph. +# Secrets are never baked into the image: search.env + db-profiles.yml mount +# read-only from ~/.config/brain. + +name: 2dph + +services: + brain: + image: ghcr.io/eslider/2dph:latest + build: + context: .. + dockerfile: docker/Dockerfile + cache_from: + - ghcr.io/eslider/2dph:cache + command: ["brain", "search", "help"] + environment: &env + HF_HOME: /data/hf + BRAIN_SEARCH_CACHE: /data/cache/web-search.sqlite + BRAIN_DB_PROFILES: /secret/db-profiles.yml + BRAIN_SEARCH_ENV: /secret/search.env + volumes: + - kb-model:/data/hf + - kb-var:/data + # corpus is read-only on the host, never written from the container + - ../..:/corpus:ro + - ~/.config/brain:/secret:ro + read_only: true + tmpfs: + - /tmp + healthcheck: + test: ["CMD", "python3", "-c", "import ladybug, model2vec, mistune; print('ok')"] + interval: 30s + timeout: 5s + retries: 3 + restart: unless-stopped + stop_grace_period: 20s + + # watcher: re-index on corpus file change (inotify via watchdog script) + brain-watch: + image: ghcr.io/eslider/2dph:latest + environment: *env + volumes: + - kb-model:/data/hf + - kb-var:/data + - ../..:/corpus:ro + - ~/.config/brain:/secret:ro + command: ["brain", "watch", "/corpus"] + read_only: true + tmpfs: + - /tmp + restart: unless-stopped + stop_grace_period: 20s + +volumes: + kb-model: + kb-var: \ No newline at end of file diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100644 index 0000000..77e50e0 --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# docker-entrypoint.sh - run 2dph bin tools inside the container. +# +# brain shell (default) +# brain search bin/kb/search +# brain index bin/kb/index +# brain watch watchdog re-indexer +# +# Usage comment starts at line 2 (self-describing convention). +set -euo pipefail + +CMD="${1:-shell}" +shift || true + +case "$CMD" in + shell) exec bash ;; + search) exec python3 /app/bin/kb/search "$@" ;; + index) exec python3 /app/bin/kb/index "$@" ;; + watch) exec python3 /app/.dockerbin/kb-watch "$@" ;; + serve) exec python3 /app/.dockerbin/serve "$@" ;; + *) echo "unknown command: $CMD" >&2; exit 2 ;; +esac \ No newline at end of file diff --git a/docker/kb-watch b/docker/kb-watch new file mode 100644 index 0000000..181d912 --- /dev/null +++ b/docker/kb-watch @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# kb-watch - re-index 2dph when corpus files change. +# +# kb-watch [dir...] [interval_seconds] +# +# Polls mtimes (no inotify deps); cheap and reliable in containers. Defaults: +# dirs = /corpus (compose) or . ; interval = 30s. +set -euo pipefail + +DEFAULT_DIRS="${KB_WATCH_DIRS:-/corpus}" +DIRS=("$@") +[[ ${#DIRS[@]} -eq 0 ]] && DIRS=(${DEFAULT_DIRS}) +INTERVAL="${KB_WATCH_INTERVAL:-30}" + +index() { python3 /app/bin/kb/index; } + +LAST_STAMP="" +while true; do + STAMP=$(find "${DIRS[@]}" -type f -newermt "-${INTERVAL} seconds" 2>/dev/null \ + | head -1 | md5sum) + if [[ -n "$STAMP" && "$STAMP" != "$LAST_STAMP" ]]; then + echo "kb-watch: changes detected, re-indexing" >&2 + index || echo "kb-watch: index failed; will retry" >&2 + LAST_STAMP="$STAMP" + fi + sleep "$INTERVAL" +done \ No newline at end of file diff --git a/docker/serve b/docker/serve new file mode 100644 index 0000000..5223874 --- /dev/null +++ b/docker/serve @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""serve - tiny read-only HTTP wrapper over bin/kb/search. + + serve [port] default 8630, binds 127.0.0.1 + +GET /health -> {"status":"ok"} +GET /search?q=... -> YAML from kb/search (transparent, cached by client) +""" +from __future__ import annotations + +import json +import sys +import urllib.parse +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8630 + + +class Handler(BaseHTTPRequestHandler): + def log_message(self, *args): # keep stdout clean + pass + + def do_GET(self): # noqa: N802 + url = urllib.parse.urlparse(self.path) + if url.path.rstrip("/") == "/health": + self._json({"status": "ok"}) + return + if url.path.rstrip("/") == "/search": + q = urllib.parse.parse_qs(url.query).get("q", [""])[0] + if not q: + self._json({"error": "q required"}, code=400) + return + self._json({"query": q, "note": "search via bin/kb/search is offline; rewire to ladybug reads"}) + return + self._json({"error": "not found"}, code=404) + + def _json(self, obj, code=200): + body = json.dumps(obj, ensure_ascii=False).encode() + self.send_response(code) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + +if __name__ == "__main__": + server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler) + print(f"serve: 127.0.0.1:{PORT}", file=sys.stderr) + server.serve_forever() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d8ec539 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "2dph" +version = "0.1.0" +description = "Evidence-first brain: LadybugDB knowledge graph + hybrid RAG over the ops stack. Facts need two sources or they are (not confirmed)." +readme = "README.md" +requires-python = ">=3.12" +license = { text = "MIT" } +dependencies = [ + "ladybug==0.19.1", + "mistune==3.3.4", + "model2vec==0.8.2", + "numpy>=2.5.2", + "pyyaml>=6.0.3", + "requests>=2.34.2", +] diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..137d666 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "include-v-in-tag": true, + "include-component-in-tag": false, + "pull-request-title-pattern": "chore${scope}: release ${version}", + "changelog-path": "CHANGELOG.md", + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance Improvements" }, + { "type": "revert", "section": "Reverts" }, + { "type": "refactor", "section": "Code Refactoring" }, + { "type": "docs", "section": "Documentation" }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "build", "section": "Build System", "hidden": true }, + { "type": "ci", "section": "Continuous Integration", "hidden": true }, + { "type": "chore", "section": "Miscellaneous", "hidden": true }, + { "type": "style", "section": "Styles", "hidden": true } + ], + "packages": { + ".": { + "package-name": "2dph" + } + } +} \ No newline at end of file diff --git a/requirements.lock.txt b/requirements.lock.txt new file mode 100644 index 0000000..994b427 --- /dev/null +++ b/requirements.lock.txt @@ -0,0 +1,70 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --python 3.12 /dev/stdin --output-file requirements.lock.txt +anyio==4.14.2 + # via httpx +certifi==2026.7.22 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.4.9 + # via requests +click==8.4.2 + # via huggingface-hub +filelock==3.32.2 + # via huggingface-hub +fsspec==2026.7.0 + # via huggingface-hub +h11==0.16.0 + # via httpcore +hf-xet==1.6.0 + # via huggingface-hub +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via huggingface-hub +huggingface-hub==1.27.0 + # via tokenizers +idna==3.18 + # via + # anyio + # httpx + # requests +jinja2==3.1.6 + # via model2vec +joblib==1.5.3 + # via model2vec +ladybug==0.19.1 + # via -r /dev/stdin +markupsafe==3.0.3 + # via jinja2 +mistune==3.3.4 + # via -r /dev/stdin +model2vec==0.8.2 + # via -r /dev/stdin +numpy==2.5.2 + # via + # -r /dev/stdin + # model2vec +packaging==26.3 + # via huggingface-hub +pyyaml==6.0.3 + # via + # -r /dev/stdin + # huggingface-hub +requests==2.34.2 + # via -r /dev/stdin +safetensors==0.8.0 + # via model2vec +tokenizers==0.23.1 + # via model2vec +tqdm==4.70.0 + # via + # huggingface-hub + # model2vec +typing-extensions==4.16.0 + # via + # anyio + # huggingface-hub +urllib3==2.7.0 + # via requests diff --git a/skills/agent-cost/SKILL.md b/skills/agent-cost/SKILL.md index cef3394..fc7cf19 100644 --- a/skills/agent-cost/SKILL.md +++ b/skills/agent-cost/SKILL.md @@ -9,10 +9,10 @@ description: >- # agent-cost ```bash -bin/agents/cost # every project, YAML -bin/agents/cost --repo brain-ui # only sessions whose cwd matches +bin/agents/cost # every project, YAML +bin/agents/cost --repo 2dph # only sessions whose cwd matches bin/agents/cost --json | jq .cursor.by_tool -bin/agents/cost --snapshot after-x --repo brain-ui # append a row to docs/CONTEXT-BUDGET.md +bin/agents/cost --snapshot after-x --repo 2dph # append a row to docs/CONTEXT-BUDGET.md ``` Reads local session storage from OpenCode and Cursor transcripts. Reports the diff --git a/skills/db-yaml/SKILL.md b/skills/db-yaml/SKILL.md index c27c1bc..7743b9b 100644 --- a/skills/db-yaml/SKILL.md +++ b/skills/db-yaml/SKILL.md @@ -8,21 +8,20 @@ description: >- # db-yaml -`agent-skills/bin/db/psql-yq` talks to Postgres and returns YAML, which is far -cheaper than a psql ASCII table and easy to slice with `yq`. +`bin/db/psql-yq` (vendored in this repo) talks to Postgres and returns YAML, +which is far cheaper than a psql ASCII table and easy to slice with `yq`. ```bash -bin/db/offline -s kunde # column list (brain-ui wrapper) -bin/db/offline -t tour -l 20 # 20 sample rows as YAML -bin/db/offline -c 'SELECT ...' # query -> YAML -bin/db/live -c 'SELECT ...' # live database instead of the snapshot +bin/db/psql-yq --profile onlyoffice -s document_asset # column list +bin/db/psql-yq --profile onlyoffice -t task_result -l 20 # sample rows as YAML +bin/db/psql-yq --profile onlyoffice -c 'SELECT ...' # query -> YAML ``` Ad-hoc targets without a profile: ```bash -agent-skills/bin/db/psql-yq --container my-pg --db app -c 'SELECT 1' -agent-skills/bin/db/psql-yq --dsn 'postgres://user@host:5432/db' -c 'SELECT 1' +bin/db/psql-yq --container my-pg --db app -c 'SELECT 1' +bin/db/psql-yq --dsn 'postgres://user@host:5432/db' -c 'SELECT 1' ``` ## Profiles diff --git a/skills/diataxis-docs/SKILL.md b/skills/diataxis-docs/SKILL.md index 8a0dcdd..16bc675 100644 --- a/skills/diataxis-docs/SKILL.md +++ b/skills/diataxis-docs/SKILL.md @@ -34,8 +34,8 @@ related: becomes a graph edge: ```bash -bin/kb/search search "deploy" --type howto -bin/kb/search search "Stecktafel" --hop 1 # follow links and related +bin/kb/search "deploy" --type howto +bin/kb/search "Stecktafel" --hop 1 # follow links and related ``` ## Audit checklist diff --git a/skills/web-search/SKILL.md b/skills/web-search/SKILL.md index b22c1c4..9e32df1 100644 --- a/skills/web-search/SKILL.md +++ b/skills/web-search/SKILL.md @@ -10,17 +10,17 @@ description: >- # web-search ```bash -bin/web/search "Pflegegrad SGB XI Einstufung" -bin/web/search "Toureffizienz" --site ticket.detective.de -bin/web/search "sqlite-vec" --category it -n 3 --json | jq -r '.results[].url' -bin/web/search "Pflegereform" --lang de --fresh year +bin/web/search "LadybugDB vector index" +bin/web/search "model2vec multilingual" --category it +bin/web/search "hypervisor" --site ops.io --json | jq -r '.results[].url' +bin/web/search "postgres partial index" --lang en --fresh year ``` ## Web or knowledge base -`bin/kb/search` holds our own facts: product docs, detective business logic, the +`bin/kb/search` holds our own facts: the ops stack, portfolio, ssh hosts, the lexicon. Go there first. Reach for `bin/web/search` when the answer is outside -our repos: legislation, vendor documentation, upstream library behaviour. +our repos: upstream library behaviour, vendor documentation, public standards. Keep the two apart. A finding is stronger when the reader can see that one source was ours and one was not. diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/web-search/__init__.py b/tools/web-search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/web-search/fixtures/healthy.json b/tools/web-search/fixtures/healthy.json new file mode 100644 index 0000000..65ac379 --- /dev/null +++ b/tools/web-search/fixtures/healthy.json @@ -0,0 +1 @@ +{"query": "Pflegegrad Test 1", "results": [{"template": "default.html", "title": "Pflegegrad-Selbsttest", "content": "Start / Pflegegrad-Selbsttest Pflegegrad-Selbsttest Beantworten Sie die folgenden Fragen zur Alltagsbew\u00e4ltigung. Klicken Sie bei Unklarheiten auf Info hinter der Auswahl und springen Sie direkt zur passenden Erkl\u00e4rung mit Beispielen, Bildern und Tabellen.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [1], "score": 1.0, "category": "general", "url": "https://www.pflegegrad-selbsttest.de/pflegegrad-test", "engine": "duckduckgo", "parsed_url": ["https", "www.pflegegrad-selbsttest.de", "/pflegegrad-test", "", "", ""]}, {"template": "default.html", "title": "Pflegegrad-Selbsttest: Orientierung, Leistungen, Antrag - alles an ...", "content": "Starten Sie mit dem Pflegegrad-Selbsttest und bereiten Sie Ihren Antrag vor: Erkl\u00e4rungen, Beispiele, Checklisten, Tabellen und Bilder - kompakt und verst\u00e4ndlich.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [2], "score": 0.5, "category": "general", "url": "https://www.pflegegrad-selbsttest.de/", "engine": "duckduckgo", "parsed_url": ["https", "www.pflegegrad-selbsttest.de", "/", "", "", ""]}, {"template": "default.html", "title": "Pflegegrad 1 \u00bb Geld \u2022 Leistungen \u2022 Voraussetzungen", "content": "Pflegegrad 1 beschreibt eine \u201egeringe Beeintr\u00e4chtigung der Selbstst\u00e4ndigkeit\" und ist der niedrigste von f\u00fcnf Pflegegraden. Doch wie bekommt man Pflegegrad 1? Und welche Anspr\u00fcche hat man mit dem niedrigsten Pflegegrad? pflege.de beschreibt die Voraussetzungen und zeigt, welche Gelder und Leistungen Sie mit Pflegegrad 1 beanspruchen k\u00f6nnen.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [3], "score": 0.3333333333333333, "category": "general", "url": "https://www.pflege.de/pflegekasse-pflegerecht/pflegegrade/pflegegrad-1/", "engine": "duckduckgo", "parsed_url": ["https", "www.pflege.de", "/pflegekasse-pflegerecht/pflegegrade/pflegegrad-1/", "", "", ""]}, {"template": "default.html", "title": "Pflegegrad Test | Pflege-Portal.com | Pflege-Portal.com", "content": "Pflegegrad Test online: kostenlose erste Einsch\u00e4tzung zum m\u00f6glichen Pflegegrad mit verst\u00e4ndlichen Fragen, Klickoptik und Ergebnisanzeige.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [4], "score": 0.25, "category": "general", "url": "https://pflege-portal.com/pflegegrad-test/", "engine": "duckduckgo", "parsed_url": ["https", "pflege-portal.com", "/pflegegrad-test/", "", "", ""]}, {"template": "default.html", "title": "Pflegegrade im \u00dcberblick \u00bb Pflegegradrechner \u2022 Leistungen", "content": "Pflegegrade Wenn Sie Leistungen der Pflegeversicherung beanspruchen m\u00f6chten, ben\u00f6tigen Sie einen der f\u00fcnf Pflegegrade (1-5). Ein Pflegegrad ist ein \u201eGrad der Pflegebed\u00fcrftigkeit\" und dr\u00fcckt aus, wie stark pflegebed\u00fcrftig jemand ist. pflege.de erkl\u00e4rt wie Sie einen Pflegegrad erhalten und welche Leistungen Sie damit beanspruchen k\u00f6nnen.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [5], "score": 0.2, "category": "general", "url": "https://www.pflege.de/pflegekasse-pflegerecht/pflegegrade/", "engine": "duckduckgo", "parsed_url": ["https", "www.pflege.de", "/pflegekasse-pflegerecht/pflegegrade/", "", "", ""]}, {"template": "default.html", "title": "Pflegegrad-Test 2026 - mit allen 64 NBA-Items", "content": "Kostenloser Pflegegrad-Selbsttest mit allen 64 offiziellen NBA-Items und tabellengetreuer Punkteberechnung. In 10 Minuten den wahrscheinlichen Pflegegrad ermitteln - anonym, ohne Anmeldung.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [6], "score": 0.16666666666666666, "category": "general", "url": "https://www.careful-service.com/pflegegrad-test", "engine": "duckduckgo", "parsed_url": ["https", "www.careful-service.com", "/pflegegrad-test", "", "", ""]}, {"template": "default.html", "title": "Pflegegrad-Rechner 2025 & 2026 - Pflegegrad ermitteln (Selbsttest ...", "content": "Pflegegrad-Rechner 2026: Ermitteln Sie Ihren Pflegegrad mit dem vereinfachten NBA-System. Selbsttest mit 6 Modulen, Punkte 0-100, Pflegegrad 1-5 und Pflegegeld-Tabelle.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [7], "score": 0.14285714285714285, "category": "general", "url": "https://www.deutschland-rechner.de/pflegegrad-rechner", "engine": "duckduckgo", "parsed_url": ["https", "www.deutschland-rechner.de", "/pflegegrad-rechner", "", "", ""]}, {"template": "default.html", "title": "Pflegegrade | 1 bis 5 | Das steht Ihnen immer zu (2026)", "content": "Pflegegrade in der Pflegeversicherung: Wie sie vergeben werden und welche Leistungen den jeweiligen Graden zugeordnet sind.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [8], "score": 0.125, "category": "general", "url": "https://www.transparent-beraten.de/private-pflegeversicherung/pflegegrade/", "engine": "duckduckgo", "parsed_url": ["https", "www.transparent-beraten.de", "/private-pflegeversicherung/pflegegrade/", "", "", ""]}, {"template": "default.html", "title": "Welcher Pflegegrad? Kostenloser Test 2026 - Ergebnis sofort", "content": "Welcher Pflegegrad trifft zu? 6 Fragen zu Mobilit\u00e4t & Selbstversorgung - sofortiges Ergebnis. Kostenlos, anonym, in 3 Minuten.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [9], "score": 0.1111111111111111, "category": "general", "url": "https://www.pflegekosten-berechnen.de/pflegegrad/pflegegrad-einschaetzung", "engine": "duckduckgo", "parsed_url": ["https", "www.pflegekosten-berechnen.de", "/pflegegrad/pflegegrad-einschaetzung", "", "", ""]}, {"template": "default.html", "title": "Was Pflegegrade bedeuten und wie die Einstufung funktioniert", "content": "Entscheidend f\u00fcr die Einstufung in einen Pflegegrad ist der Grad der Selbstst\u00e4ndigkeit. Dabei soll der ganze Mensch betrachtet und auch geistige Einschr\u00e4nkungen ber\u00fccksichtigt werden. Der Pflegegradrechner der Verbraucherzentralen unterst\u00fctzt Betroffene und Angeh\u00f6rige.", "img_src": "", "iframe_src": "", "audio_src": "", "thumbnail": "", "publishedDate": null, "pubdate": "", "length": null, "views": "", "author": "", "metadata": "", "priority": "", "engines": ["duckduckgo"], "open_group": false, "close_group": false, "positions": [10], "score": 0.1, "category": "general", "url": "https://www.verbraucherzentrale.de/wissen/gesundheit-pflege/pflegeantrag-und-leistungen/was-pflegegrade-bedeuten-und-wie-die-einstufung-funktioniert-13318", "engine": "duckduckgo", "parsed_url": ["https", "www.verbraucherzentrale.de", "/wissen/gesundheit-pflege/pflegeantrag-und-leistungen/was-pflegegrade-bedeuten-und-wie-die-einstufung-funktioniert-13318", "", "", ""]}], "answers": [], "corrections": [], "infoboxes": [], "suggestions": [], "unresponsive_engines": [["brave", "too many requests"], ["startpage", "Suspended: CAPTCHA"]]} \ No newline at end of file diff --git a/tools/web-search/fixtures/throttled.json b/tools/web-search/fixtures/throttled.json new file mode 100644 index 0000000..04c7f4d --- /dev/null +++ b/tools/web-search/fixtures/throttled.json @@ -0,0 +1 @@ +{"query": "Pflegegrad Test 4", "results": [], "answers": [], "corrections": [], "infoboxes": [], "suggestions": [], "unresponsive_engines": [["brave", "Suspended: too many requests"], ["duckduckgo", "CAPTCHA"], ["startpage", "Suspended: CAPTCHA"]]} \ No newline at end of file diff --git a/tools/web-search/test_websearch.py b/tools/web-search/test_websearch.py new file mode 100644 index 0000000..a4d131f --- /dev/null +++ b/tools/web-search/test_websearch.py @@ -0,0 +1,110 @@ +"""Tests for the SearXNG client. No network: two recorded responses stand in. + +Run: python3 -m unittest discover -s tools -t . +""" +import sys +import json +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import websearch as ws + +FIXTURES = Path(__file__).resolve().parent / "fixtures" +HEALTHY = json.loads((FIXTURES / "healthy.json").read_text()) +THROTTLED = json.loads((FIXTURES / "throttled.json").read_text()) + + +class Classify(unittest.TestCase): + """An empty result set is not evidence of absence. + + The instance answers 200 with `results: []` when it throttles us, so calling + that "no matches" would make an agent conclude something false. + """ + + def test_healthy_response_is_ok(self): + self.assertEqual(ws.classify(HEALTHY), "ok") + + def test_empty_response_is_throttled_not_empty(self): + self.assertEqual(ws.classify(THROTTLED), "throttled") + + def test_status_is_never_the_word_empty(self): + self.assertNotIn(ws.classify(THROTTLED), ("empty", "no_results")) + + +class Project(unittest.TestCase): + def test_keeps_only_the_fields_worth_context(self): + out = ws.project(HEALTHY, limit=3) + self.assertEqual(out["status"], "ok") + self.assertEqual(len(out["results"]), 3) + self.assertEqual(set(out["results"][0]), {"rank", "title", "url", "snippet", "engine"}) + + def test_snippet_is_trimmed(self): + out = ws.project(HEALTHY, limit=5, snippet_chars=40) + self.assertTrue(all(len(r["snippet"]) <= 43 for r in out["results"])) + + def test_projection_is_far_cheaper_than_the_raw_payload(self): + raw = len(json.dumps(HEALTHY)) + small = len(json.dumps(ws.project(HEALTHY, limit=5))) + self.assertLess(small * 3, raw) + + def test_throttled_projection_carries_the_engine_reasons(self): + out = ws.project(THROTTLED, limit=5) + self.assertEqual(out["status"], "throttled") + self.assertEqual(out["results"], []) + self.assertTrue(out["unresponsive"]) + + +class CacheKey(unittest.TestCase): + def test_same_question_same_key(self): + self.assertEqual(ws.cache_key("Pflegegrad", {}), ws.cache_key("Pflegegrad", {})) + + def test_case_and_padding_do_not_matter(self): + self.assertEqual(ws.cache_key(" Pflegegrad ", {}), ws.cache_key("pflegegrad", {})) + + def test_parameters_change_the_key(self): + self.assertNotEqual(ws.cache_key("x", {"lang": "de"}), ws.cache_key("x", {})) + + def test_parameter_order_does_not_change_the_key(self): + self.assertEqual(ws.cache_key("x", {"a": "1", "b": "2"}), + ws.cache_key("x", {"b": "2", "a": "1"})) + + +class PhiGuard(unittest.TestCase): + """The query leaves this host, so client data must never reach it.""" + + def test_plain_technical_query_passes(self): + self.assertIsNone(ws.phi_reason("Pflegegrad SGB XI Einstufung")) + self.assertIsNone(ws.phi_reason("site:ticket.detective.de Toureffizienz")) + + def test_long_digit_run_is_refused(self): + self.assertIsNotNone(ws.phi_reason("Kunde 4711220385 Adresse")) + + def test_insurance_number_is_refused(self): + self.assertIsNotNone(ws.phi_reason("KV-Nr A123456789")) + + def test_street_with_house_number_is_refused(self): + self.assertIsNotNone(ws.phi_reason("Hauptstraße 14 Berlin")) + self.assertIsNotNone(ws.phi_reason("Lindenstr. 7")) + + def test_personalnummer_is_refused(self): + self.assertIsNotNone(ws.phi_reason("Personalnummer 12")) + + def test_short_numbers_are_fine(self): + self.assertIsNone(ws.phi_reason("SGB XI Paragraph 45b")) + + +class Throttle(unittest.TestCase): + def test_waits_the_remainder_of_the_interval(self): + self.assertAlmostEqual(ws.wait_for(last=100.0, now=104.0, interval=10.0), 6.0) + + def test_no_wait_once_the_interval_passed(self): + self.assertEqual(ws.wait_for(last=100.0, now=130.0, interval=10.0), 0.0) + + def test_no_wait_on_a_first_call(self): + self.assertEqual(ws.wait_for(last=None, now=130.0, interval=10.0), 0.0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/web-search/websearch.py b/tools/web-search/websearch.py new file mode 100644 index 0000000..b0051d6 --- /dev/null +++ b/tools/web-search/websearch.py @@ -0,0 +1,161 @@ +"""SearXNG client that is safe for agents to share. + +Three things make this more than a curl wrapper: + + * An empty result set from this instance usually means "throttled", not "no + matches". Reporting it as absence would make an agent state something false, + so `classify` never returns a word that sounds like a negative finding. + * Queries leave the host, so `phi_reason` refuses anything that smells like + client data before it reaches an external engine. + * Results are cached and calls are serialised, because the instance suspends + engines under load. +""" +from __future__ import annotations + +import hashlib +import json +import re +import sqlite3 +import time +from pathlib import Path + +SNIPPET_CHARS = 150 +DEFAULT_LIMIT = 5 +MIN_INTERVAL = 10.0 +CACHE_TTL = 7 * 24 * 3600 +RETRY_BACKOFF = (20.0, 60.0) + + +# -------------------------------------------------------------------------- +# response handling +# -------------------------------------------------------------------------- + +def classify(payload: dict) -> str: + """`ok` when at least one engine answered, `throttled` otherwise.""" + return "ok" if payload.get("results") else "throttled" + + +def project(payload: dict, limit: int = DEFAULT_LIMIT, + snippet_chars: int = SNIPPET_CHARS) -> dict: + """Keep the few fields worth spending context on.""" + status = classify(payload) + results = [] + for rank, item in enumerate(payload.get("results", [])[:limit], start=1): + snippet = re.sub(r"\s+", " ", item.get("content") or "").strip() + if len(snippet) > snippet_chars: + snippet = snippet[:snippet_chars].rstrip() + "..." + results.append({ + "rank": rank, + "title": item.get("title", ""), + "url": item.get("url", ""), + "snippet": snippet, + "engine": item.get("engine", ""), + }) + out = { + "query": payload.get("query", ""), + "status": status, + "results": results, + } + unresponsive = [f"{name}: {reason}" for name, reason in + payload.get("unresponsive_engines", [])] + if unresponsive: + out["unresponsive"] = unresponsive + if status == "throttled": + out["note"] = ("no engine answered - this is a throttled instance, " + "not evidence that nothing exists") + return out + + +# -------------------------------------------------------------------------- +# cache key and throttling +# -------------------------------------------------------------------------- + +def cache_key(query: str, params: dict) -> str: + norm = " ".join(query.lower().split()) + stable = json.dumps(params, sort_keys=True, ensure_ascii=False) + return hashlib.sha256(f"{norm}\x00{stable}".encode()).hexdigest() + + +def wait_for(last: float | None, now: float, interval: float = MIN_INTERVAL) -> float: + """Seconds to sleep so that calls stay `interval` apart.""" + if last is None: + return 0.0 + return max(0.0, interval - (now - last)) + + +# -------------------------------------------------------------------------- +# PII guard +# -------------------------------------------------------------------------- + +PII_PATTERNS = [ + (re.compile(r"\d{6,}"), "a run of six or more digits looks like an ID"), + (re.compile(r"\bpersonalnummer\b", re.I), "Personalnummer is staff data"), + (re.compile(r"\bkv[-\s]?nr\b", re.I), "KV-Nr is an insurance number"), + (re.compile(r"\bversichertennummer\b", re.I), "insurance number"), + (re.compile(r"\b[A-Za-zÄÖÜäöüß]+(?:stra(?:ss|ß)e|str\.)\s*\d+", re.I), + "a street with a house number looks like an address"), + (re.compile(r"\bgeb(?:urtsdatum)?\.?\s*\d{1,2}[./]\d{1,2}[./]\d{2,4}", re.I), + "a date of birth"), +] + + +def phi_reason(query: str) -> str | None: + """Why this query must not be sent, or None when it is safe.""" + for pattern, reason in PII_PATTERNS: + if pattern.search(query): + return reason + return None + + +# -------------------------------------------------------------------------- +# cache storage +# -------------------------------------------------------------------------- + +CACHE_SCHEMA = """ +CREATE TABLE IF NOT EXISTS responses ( + key TEXT PRIMARY KEY, + fetched REAL NOT NULL, + payload TEXT NOT NULL +); +CREATE TABLE IF NOT EXISTS meta ( + key TEXT PRIMARY KEY, + value REAL NOT NULL +); +""" + + +def open_cache(path: Path) -> sqlite3.Connection: + path.parent.mkdir(parents=True, exist_ok=True) + conn = sqlite3.connect(path, timeout=30) + conn.row_factory = sqlite3.Row + conn.executescript(CACHE_SCHEMA) + return conn + + +def cache_get(conn: sqlite3.Connection, key: str, ttl: float = CACHE_TTL, + now: float | None = None) -> dict | None: + now = time.time() if now is None else now + row = conn.execute("SELECT fetched, payload FROM responses WHERE key = ?", + (key,)).fetchone() + if row is None or now - row["fetched"] > ttl: + return None + return json.loads(row["payload"]) + + +def cache_put(conn: sqlite3.Connection, key: str, payload: dict, + now: float | None = None) -> None: + now = time.time() if now is None else now + conn.execute("INSERT OR REPLACE INTO responses (key, fetched, payload) VALUES (?, ?, ?)", + (key, now, json.dumps(payload, ensure_ascii=False))) + conn.commit() + + +def last_call(conn: sqlite3.Connection) -> float | None: + row = conn.execute("SELECT value FROM meta WHERE key = 'last_call'").fetchone() + return row["value"] if row else None + + +def mark_call(conn: sqlite3.Connection, now: float | None = None) -> None: + now = time.time() if now is None else now + conn.execute("INSERT OR REPLACE INTO meta (key, value) VALUES ('last_call', ?)", (now,)) + conn.commit() diff --git a/tools/yamlout.py b/tools/yamlout.py new file mode 100644 index 0000000..4e0f0ea --- /dev/null +++ b/tools/yamlout.py @@ -0,0 +1,48 @@ +"""Minimal YAML emitter. + +Agents read YAML more cheaply than JSON and the output stays diffable. This is +deliberately tiny: it emits the shapes these tools produce, nothing more. +""" +from __future__ import annotations + +import json + + +def to_yaml(node, indent: int = 0) -> str: + pad = " " * indent + if isinstance(node, dict): + if not node: + return f"{pad}{{}}\n" + out = "" + for key, value in node.items(): + if isinstance(value, (dict, list)) and value: + out += f"{pad}{key}:\n{to_yaml(value, indent + 1)}" + elif isinstance(value, (dict, list)): + out += f"{pad}{key}: {'{}' if isinstance(value, dict) else '[]'}\n" + else: + out += f"{pad}{key}: {scalar(value)}\n" + return out + if isinstance(node, list): + out = "" + for item in node: + if isinstance(item, dict): + out += f"{pad}-\n{to_yaml(item, indent + 1)}" + else: + out += f"{pad}- {scalar(item)}\n" + return out + return f"{pad}{scalar(node)}\n" + + +def scalar(value) -> str: + if value is None: + return "null" + if isinstance(value, bool): + return "true" if value else "false" + if isinstance(value, (int, float)): + return str(value) + text = str(value) + if "\n" in text: + return json.dumps(text, ensure_ascii=False) + if text == "" or any(ch in text for ch in ":#'\"[]{}&*!|>%@`") or text != text.strip(): + return json.dumps(text, ensure_ascii=False) + return text diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..2e4cc51 --- /dev/null +++ b/uv.lock @@ -0,0 +1,604 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "2dph" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "ladybug" }, + { name = "mistune" }, + { name = "model2vec" }, + { name = "numpy" }, + { name = "pyyaml" }, + { name = "requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "ladybug", specifier = "==0.19.1" }, + { name = "mistune", specifier = "==3.3.4" }, + { name = "model2vec", specifier = "==0.8.2" }, + { name = "numpy", specifier = ">=2.5.2" }, + { name = "pyyaml", specifier = ">=6.0.3" }, + { name = "requests", specifier = ">=2.34.2" }, +] + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/57/3ba6e6cb097f85b855b00163d169f35365f44277df044dcf96d55b8f62a3/filelock-3.32.2.tar.gz", hash = "sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8", size = 217172, upload-time = "2026-07-29T22:46:04.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/e8/72f8cef9fdfeffe06213fe8508039396ee48daa0e3259457ed766173bfd6/filelock-3.32.2-py3-none-any.whl", hash = "sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82", size = 98830, upload-time = "2026-07-29T22:46:03.52Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527, upload-time = "2026-08-03T22:33:13.243Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/62/3c062f593bd92ef4e77a0ef39541e3d82a0a1d3947c8a777a02a13a27828/hf_xet-1.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:70cbb9c896901600128cb9b6f06e132954fbede1db30f31f7c6c63f84cb7c31d", size = 4074584, upload-time = "2026-08-03T22:32:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1e/c0ad437dd267a8e435bef594acf781bbc3874ff0b6435b4962d03ecf7cc4/hf_xet-1.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23379c2f9ec8696d952b16414a2bae72cad86a52df869b050698ba60f538c675", size = 3867381, upload-time = "2026-08-03T22:32:49.049Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ee/7c0d7b6ab336167531b1c30af2af003f054af4c749becbd7209ae33a77c3/hf_xet-1.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f7278c05c22fd60cb436cda1269649b3e81db65ecdc8496e5e164aa4143e7b", size = 4453982, upload-time = "2026-08-03T22:32:50.568Z" }, + { url = "https://files.pythonhosted.org/packages/63/06/ad8eab1c9525246650cbaa821caa3cdbaca734ab1a5b8c91bea09cbd8d69/hf_xet-1.6.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:948f15d3a9545cfe5932f6bd8b440f6ae630aee108f14b7bd6c561f7c2dcc522", size = 4249445, upload-time = "2026-08-03T22:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/d8/26/1eee8aedb0dafc1ab9717dc9ac602cde33361b232dc06803f1f6ed18b58c/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5153e6bb103ad49d6ea9f1b2e230db5a2ea32551ad09a706d2f61d7c7c80d80e", size = 4451099, upload-time = "2026-08-03T22:32:54.114Z" }, + { url = "https://files.pythonhosted.org/packages/67/57/0b88af1f194ab6c9c650547d9cc06bfeaab836ae4dcdb331676bfb8be95a/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:35cec30d75c6f9eb9c16a77cef68e85a103b72e24d4b473714ec9ff06428bab9", size = 4664712, upload-time = "2026-08-03T22:32:55.547Z" }, + { url = "https://files.pythonhosted.org/packages/53/a0/26b717a9d1840e8abf48dcec64b5ed8fbe472671d38ad28d30e147132b33/hf_xet-1.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5789835d7c6bc9436962853192082374297fb72d7eff7e7762ec25ceb7e25338", size = 4025906, upload-time = "2026-08-03T22:32:57.391Z" }, + { url = "https://files.pythonhosted.org/packages/49/f6/4a9966633c6fef83af997e2cff68ec1963676d412bdfd096df2a93b8e185/hf_xet-1.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:75765820ce4700db3750c94acc8fe27c5fae4c9ec000a0dbac3ca082acf97765", size = 3849221, upload-time = "2026-08-03T22:32:59.123Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729, upload-time = "2026-08-03T22:33:00.721Z" }, + { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287, upload-time = "2026-08-03T22:33:02.251Z" }, + { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663, upload-time = "2026-08-03T22:33:03.802Z" }, + { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538, upload-time = "2026-08-03T22:33:05.287Z" }, + { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520, upload-time = "2026-08-03T22:33:06.81Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937, upload-time = "2026-08-03T22:33:08.616Z" }, + { url = "https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b", size = 4033128, upload-time = "2026-08-03T22:33:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3", size = 3859359, upload-time = "2026-08-03T22:33:11.725Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/9b/ddf3d02a8681f1b9ce52fda03d755dad6b74c4f8172304c4c8d2975450f9/huggingface_hub-1.27.0.tar.gz", hash = "sha256:c1fed40ea82a6b41b477f5243546549b792ae0a93abcea608cff66089bf8f8df", size = 942668, upload-time = "2026-08-07T12:48:05.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d8/95b735e183957c1f26d94c52977f09d466d55119cbbc1558ea4975e4c216/huggingface_hub-1.27.0-py3-none-any.whl", hash = "sha256:7df6827c2f956c60fbaa64646e979e566db76f619dd0a9729dfb8c5a3eb4f68d", size = 784926, upload-time = "2026-08-07T12:48:02.905Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "ladybug" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/c6/300b8b384f58a81d42d70ae26c7e7c4920c84502546b78278dd32dc39dfd/ladybug-0.19.1.tar.gz", hash = "sha256:cf3a510e396794ba4702ed0423b81a5502070a9dfbdb6ea97a5766f27565ec05", size = 10306327, upload-time = "2026-08-04T23:32:43.263Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/4b/8f4dccde4c412494d62fd7fa07ffc630070b1d95ca7655cdbeb3f45ce781/ladybug-0.19.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e950a746f1e5b343a6ee1602c385c89466db5b555e6c2a4ec17b5ef26c12fd7a", size = 4571087, upload-time = "2026-08-04T23:31:46.559Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/887e76e65850f4802f60303e5aebd939e8d734b077a3403c7a3473589a8f/ladybug-0.19.1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:8ad6cac8764343a99fbdb6d31a1325f5b5da51ab7d18093d205bf3a275db32d8", size = 4914114, upload-time = "2026-08-04T23:31:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/4d/93/e9a49e381c0f51831b88cac9d24ab2e87d0fff6c53ee94931dfcd0c880c0/ladybug-0.19.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9df804fcebbad6f73e9ba03325481e9f61f4e3a4dc1ba8f15752eca4c516ce6b", size = 7375140, upload-time = "2026-08-04T23:31:50.636Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ca/475d42aa9ae2dda642b7730512c5240c54981ca88d99daef497ff51db393/ladybug-0.19.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29ca478e3e65504e2666844ecd6d354868c58152c92c2a694f824d04646d3758", size = 8277454, upload-time = "2026-08-04T23:31:53.046Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/8384e836b22fe18749b022f05a25a0a20333212ed8d331d6e736577cbfe4/ladybug-0.19.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f5a0eb76d426de6e0c945e209ced63ceb07d58da738a0d8307975e896d2a3b0e", size = 8204215, upload-time = "2026-08-04T23:31:55.146Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2b/941534b20c003cea6b1c6af751107f933e8db075356ee582e677ce3e2423/ladybug-0.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b2060283a130d12946c5636caba6c285fef9f50f4c200e6627d823911e9aaa1", size = 9135055, upload-time = "2026-08-04T23:31:57.455Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2b/4a6e8bf0896a39778195d595061e46711fb494d43651c0e71c879dc08251/ladybug-0.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:34189d55118cbcaedf538365cf74cf0c0d782adcd978df684518bce182ccada2", size = 5730274, upload-time = "2026-08-04T23:31:59.909Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e7/5e49d9d96b3903fde53bb3c1d39bf00c38e43d9cc52d9961eecfc4daa979/ladybug-0.19.1-cp312-cp312-win_arm64.whl", hash = "sha256:45712f36efb8073752249f8d55d840f47412d0a99366d33d1c2d46b24797d8f1", size = 8215369, upload-time = "2026-08-04T23:32:02.205Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9e/a01e69840aa70d3b41cf52ecf6337c3e8d65e63085fe9b6012c8bece64c1/ladybug-0.19.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:727198319367f824c7957a8d8f9e4bb996cefedb0be6ea5c80276c9ac017ae60", size = 4571064, upload-time = "2026-08-04T23:32:04.492Z" }, + { url = "https://files.pythonhosted.org/packages/38/19/8699906ab2dc7bdd897f4badad8ff5da84100db45846bedc8a71d9f327ea/ladybug-0.19.1-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:3ae8298002a3c53331fe06306be0d5cc98462fa5255ac257de89b1cb5da01f02", size = 4914369, upload-time = "2026-08-04T23:32:06.561Z" }, + { url = "https://files.pythonhosted.org/packages/50/22/554ff4cda4d39d6176527538af1910099bb0026aa10c2e4458643ea80a85/ladybug-0.19.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c97e78ffd4cad73789b40b5c18f1934ee712a063abb8aa093fb22d47925191", size = 7374487, upload-time = "2026-08-04T23:32:08.538Z" }, + { url = "https://files.pythonhosted.org/packages/61/f0/ffbe872b7b15304784bcc5049ff13c0e372d756bc32466d6b6c9a6875a83/ladybug-0.19.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7068edcaf72bd018568832e1d42983f203541e1db591128c7578e54a36123e7c", size = 8277590, upload-time = "2026-08-04T23:32:11.022Z" }, + { url = "https://files.pythonhosted.org/packages/27/c3/35c9b9a02450e826cf294d60b94beae5b47e9ce1496f132b3131ff720c46/ladybug-0.19.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:452086f9f7e2ac9bc36776a9f0d2a41866cda49228e341b3ff97a82a46dfc9f1", size = 8202764, upload-time = "2026-08-04T23:32:13.289Z" }, + { url = "https://files.pythonhosted.org/packages/f1/bb/7c11ac412c7dc66ff84a26d7231027a9122e4491c9ae37567655df001e9c/ladybug-0.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7261fe99ea1594deaafa717ee17d1ee4bf39867b99dec5aa36413cd0d092f59a", size = 9134109, upload-time = "2026-08-04T23:32:15.991Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8c/b365bd78a498151e234972ab78c009d625dee0ceecf3ada6463545020d68/ladybug-0.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:fffc3468d7033d0688b3c7957e1b59e987a362e6e281d8c77b4e4eedaf1b674e", size = 5730230, upload-time = "2026-08-04T23:32:18.529Z" }, + { url = "https://files.pythonhosted.org/packages/5b/df/29c1716a36cbccd5629436cb076f84f3ab4fb08bd890c681b8ff2ceff7b9/ladybug-0.19.1-cp313-cp313-win_arm64.whl", hash = "sha256:7902ef0bbce2405f220b66668f26ef22dc1b69a2f6b946d89a50956b8b170aa9", size = 8215352, upload-time = "2026-08-04T23:32:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e1/817caca958a68585052236020dce6b6aac00bd3d5a4ae3e87dce9f864070/ladybug-0.19.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:cf4239deeadf45252563059a9185b6955f5c80b370c49fc5097bb704da8c821c", size = 4571434, upload-time = "2026-08-04T23:32:23.178Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/e54cbbe1c29aea3f45591f4e368ee95ffae305308c6a54e519a62cedeaef/ladybug-0.19.1-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:f4d10576fb2188574f19dd4efa99a79e5de5d2e0a6fd3e3def0fe3b1f80fff8d", size = 4914339, upload-time = "2026-08-04T23:32:25.227Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/8062703f0976062951797c355042cb3861d879b4fdaf874a432430e1ab0c/ladybug-0.19.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c720e019ebcb7c4fb2e3b5a274c6d2034da72fdf43eeaa6b2a6805d48fb3c91", size = 7375461, upload-time = "2026-08-04T23:32:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/77add7f65f72fd8d5e5b156701e1d16926a617de9075cf655951b5c4bd72/ladybug-0.19.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8446e69edc766b9acf61a6af82af6eb0570067196c5c94716a8865bf7ea9b555", size = 8277802, upload-time = "2026-08-04T23:32:29.629Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/f26ab4436f89f662c265e900e450aa27ec40f4f9d949fb27a9d4017292e2/ladybug-0.19.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3d9b0deffe428524f39547668800a81734157340987aeb66ed01f879c3f277dc", size = 8203681, upload-time = "2026-08-04T23:32:32.014Z" }, + { url = "https://files.pythonhosted.org/packages/55/39/ab516c40b96580e493a770ae49fc214fdc2e4a6e6f8f0d44284a2ef631ac/ladybug-0.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e334151ca486e84efa4a5af10861728fe395f000e44bd050a94d1e877b143df0", size = 9134762, upload-time = "2026-08-04T23:32:35.03Z" }, + { url = "https://files.pythonhosted.org/packages/16/fb/e6ed7abcba202d5fab76ec5a6acfb3358e6ea7e5730059c96593acb7deb6/ladybug-0.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:b213587ae4f86e34008f8d4a44e1ac8467003c5c03a19244d9ee4e3e431c5775", size = 5904867, upload-time = "2026-08-04T23:32:37.923Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a6/94bdc981fe0c55537b84c0549472fafcdd14eb2ca4b179ee34bbd536dc76/ladybug-0.19.1-cp314-cp314-win_arm64.whl", hash = "sha256:db24c840bbb931b7f2fd959acd1e55360b997576c9b18e5ac1a1cd25d7b7ea8b", size = 8472851, upload-time = "2026-08-04T23:32:40.304Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { 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 = "mistune" +version = "3.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/92/328a294a6de83bacb95bed01f04e0eaff4e3616ee359fc821a5dfc539b02/mistune-3.3.4.tar.gz", hash = "sha256:58b5c96d6fcb61190dfe5fae498d2b2065f99cf61e9649418fd54cf1ada86dfe", size = 121426, upload-time = "2026-07-22T05:22:30.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl", hash = "sha256:ee015381e955e370962968befe1d729ab60fafb6a715ac6751763fbce38c8d4a", size = 66862, upload-time = "2026-07-22T05:22:29.419Z" }, +] + +[[package]] +name = "model2vec" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "joblib" }, + { name = "numpy" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/c8/8efa9e52d210957b02b1c711f4e3b88d1e9eb5485d1869aaa9833d0cd1c5/model2vec-0.8.2.tar.gz", hash = "sha256:4e88d1a5eb2136475ebd90505689046f61caf25c69371c331f21c6499637c110", size = 4532469, upload-time = "2026-05-29T12:01:21.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/a1/38101b223fb6cea0f2e401fbacdf4f6121628d45dc9329b255d7ac843234/model2vec-0.8.2-py3-none-any.whl", hash = "sha256:f0ecfe994316e401dca583fbf6dd22079d308c05717dd36d40bff60f265431cf", size = 54749, upload-time = "2026-05-29T12:01:19.411Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, + { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, + { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/c6/28de0191c5f82b7d42a0a51390ba98587048aa93a39fafb05bdbe6e8d00c/numpy-2.5.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:078f9b027b478c9379b9677babbf0f8b8f1ecfada27636d7b9a93990c638739f", size = 16885274, upload-time = "2026-08-09T13:47:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/973ca116000d244897e468ea1aff30b589e5022e3c8744b71706fe33bd57/numpy-2.5.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:50a68f4bacd8a2b33d8da3d2269d0d78500f86ea582e4786dc10f5ef2c2c6842", size = 11907846, upload-time = "2026-08-09T13:47:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/78/d9/8c4b3937ef204cb2fd88d389ccd0f265a2ffb11f35a01d2064cf46714bd6/numpy-2.5.2-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:e79aba74ffaf5f78a050d777c184cddf8fdffabab38acf5f3ef1fecbc17895d6", size = 5354892, upload-time = "2026-08-09T13:47:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/b6ee65ea2999fdb7023935e108e6fb776ee4082aa15f159acfa857e578c8/numpy-2.5.2-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:9a0731745a72a184490a582fb4af2533512bd071ace67785b5fdffc0ae58dce8", size = 6679309, upload-time = "2026-08-09T13:47:20.456Z" }, + { url = "https://files.pythonhosted.org/packages/43/f3/acb18d8b137a393c8e7803a8c994c9e64bde3930692a69d826993113a159/numpy-2.5.2-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec954036759bcee3aa484f8603bd9c14f3e776293b85578b8734c2d72777c69", size = 15625850, upload-time = "2026-08-09T13:47:24.365Z" }, + { url = "https://files.pythonhosted.org/packages/a9/bf/a8e9bb0db815a0e265b5744ebedd3af0bd5faad8604e5b50a1cd012f3c91/numpy-2.5.2-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc649493697006bc90614a5f0bbc8cb3cb1866715c474e473694968d7e6b99ab", size = 16713664, upload-time = "2026-08-09T13:47:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/6e913736b3dd6582344af32418b5fb9dab34282e8a8174ae1d54ceb0fc13/numpy-2.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cf7de32f486e4ac9e2d93b810f9e9ac72a728dd46a32a0bb403222f27f653514", size = 16986749, upload-time = "2026-08-09T13:47:31.541Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/7d3b23eff5c7428ef6c01e6f7052bb60d504c4d33e317b36b8959c24ad97/numpy-2.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2ffa7bacab3e2ee1b19ed31766bb60bb380b68c23f051e199c5cc598afd68710", size = 18470495, upload-time = "2026-08-09T13:47:35.364Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a4/68a321d825374f6eb677ffe8ef8c6b9a328304e6fd2e39d9530822776607/numpy-2.5.2-cp315-cp315-win32.whl", hash = "sha256:6b588cc8f902d6bff201c19fd00c43ab8545671e3554d014e12e14139e5e8617", size = 6120696, upload-time = "2026-08-09T13:47:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/c8/23/deafbb1700f79fae9cd1e91220f133d124cc267de1b584da3fbf6db2f6cd/numpy-2.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:07d4e89f3a9ab0a9ba24264ccdb642b3dd951b2281e8883a5481a4aa79cc31a7", size = 12597324, upload-time = "2026-08-09T13:47:41.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/3272ba105e3bbbdaeb11357eda31e7a6825ffe159e8171665660299a948f/numpy-2.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:a610dc7e3c52edd39c2bc2375ff9c3fd59cb3ad00e4472d36f83bc1457145788", size = 10680466, upload-time = "2026-08-09T13:47:44.873Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0e/58370637b1bb70a5c9ce2b43f4b521ccb224e36ccb76a6596b17ae4b447c/numpy-2.5.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:40f4d451aed46a8046a1aae41c4e55fb3612273df9c502480135e1501576a34b", size = 16993947, upload-time = "2026-08-09T13:47:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/93/2abcb807712b289d6d60fe4cf30532f98974a8396d885650f3ba5a13026e/numpy-2.5.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c081cbe16ba1ab53078e5ff29013621e33c509eedab055775d956427712c236e", size = 12025331, upload-time = "2026-08-09T13:47:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3a/2898e003a5fbaf87e76c039b4ee1f5eb390471b4ffe74887c1f34c4e791e/numpy-2.5.2-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:0090ccdd57ec2703e9b49d0bf554767370581c1dd0a6b2bb2b2d9def317d042a", size = 5472336, upload-time = "2026-08-09T13:47:55.403Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/23f69d07c544597b29758b31b55c27dc9d541012a2c1496189fef702aec2/numpy-2.5.2-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:6a9bb119fb8dd21ba30b3f0e555b7e2b081bd9883af21ec9c1c633d161cda3a8", size = 6788387, upload-time = "2026-08-09T13:47:58.192Z" }, + { url = "https://files.pythonhosted.org/packages/15/ea/c0dbdbcf22f43782510a3e492dd3da73c6112b69cac8929d16d127536fc4/numpy-2.5.2-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a839318485284a6fb31be4f8f2c91c8f2cb22f4543c4a8903f12b0671ffe07cc", size = 15667096, upload-time = "2026-08-09T13:48:01.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5e/29c73c31748cdb0f7566642125ba17fd5b56780cddf891b085dab27e4466/numpy-2.5.2-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba0a474801b8dc67b66bf465548abc90e82b44d2611b5770f33008dcabffe8ec", size = 16751730, upload-time = "2026-08-09T13:48:05.706Z" }, + { url = "https://files.pythonhosted.org/packages/47/95/02501e8454796bb58dadf7a99d3181e0b464bf264e1003039572f9779fac/numpy-2.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a4035ae1129ff8777f08bfbd44f1e5d8e9c049ce0c2dd78fc0d92c13e7251c0", size = 17038686, upload-time = "2026-08-09T13:48:09.627Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b5/53a681d91b5c82687067d8ea5035e02d917b5509d6f334cb06484a954714/numpy-2.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:77843ca236b777e67f8d6b3660ea116e499612703a0ecd7093f316201eb9d8e2", size = 18507727, upload-time = "2026-08-09T13:48:13.744Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/6e11443f7b64ee376c860506091103bf68f92d2cab9e8d96d4501babf07c/numpy-2.5.2-cp315-cp315t-win32.whl", hash = "sha256:7354826bc6f8f69402e9b7fe28d15fcd34feebd74f856f111585c5b0c9fb0251", size = 6269775, upload-time = "2026-08-09T13:48:17.543Z" }, + { url = "https://files.pythonhosted.org/packages/f1/18/195d6b86cd72dbbc501edfa778005fa6b87afd34c153e46028cd3a0938f4/numpy-2.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:e5651f3f87add730ee6608d915009e19c911fba0cb000c7e3ea994b7d768eb12", size = 12782559, upload-time = "2026-08-09T13:48:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/458c344f0f0c178f4481dad5cca790626ffe4c34eabf9467069d06ee4999/numpy-2.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:5f8e00be2ec6f45f4e8a41a527f68d44a7d96fee92a650e4d8b1326f77f61e6e", size = 10748103, upload-time = "2026-08-09T13:48:24.21Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" }, + { url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" }, + { url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" }, +] + +[[package]] +name = "tqdm" +version = "4.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +]