diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 280801786..c8c90eead 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,35 @@ jobs: - name: Public tree audit run: bash scripts/audit_public_tree.sh + documentation-history: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Audit documents in every new commit + env: + DOCUMENTATION_BASE: ${{ github.event.pull_request.base.sha }} + run: bash scripts/audit_public_tree.sh --history-base "$DOCUMENTATION_BASE" + + sandbox-contracts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install optional runtime and practice + run: python -m pip install -e '.[dev,sandbox]' -e practices/h_cluster + - name: SDK contracts without allocation + run: python scripts/sandbox_sdk_contract.py + - name: Optional lifecycle and practice tests + run: python -m pytest tests/harborrl/test_sandbox_cleanup.py practices/h_cluster/tests + package: runs-on: ubuntu-latest needs: cpu diff --git a/.gitignore b/.gitignore index 2f4e7f480..7755b672b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,20 @@ harbor_datasets/ .tools/ .worktrees/ benchmarks/harbor/0.5-mvp/ -docs/ +docs/* +!docs/agents/ +docs/agents/* +!docs/agents/environment-check.md +!docs/configuration-wizard.md +!docs/deployment.md +!docs/sandbox-rollout.md +!docs/train-inference-consistency.md +!docs/web-console.md +practices/**/VALIDATION.md +practices/**/HISTORICAL_VALIDATION.md +integration-report-*.md +integration-roadmap-*.md +*.bundle local/ configs/site/ examples/**/private* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98b03e866..46f7e2791 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,25 @@ HarborRL accepts changes through pull requests from a public branch. 6. Submit a reviewable commit series and describe external versions, task provenance, and any acceptance evidence. +## Public documentation + +Commit documentation that helps users install, configure, run or troubleshoot +HarborRL. Keep development plans, integration reports, experiment logs and +machine-specific validation records outside the repository. Summarize current +support and limitations in the relevant user guide without development history. + +The public-document allowlist is maintained in `scripts/audit_public_tree.sh`. +Update it deliberately when adding a user guide. Check both the final tree and +every new commit before opening a pull request: + +```bash +bash scripts/audit_public_tree.sh --history-base origin/main +``` + +Deleting a private document in a later commit does not remove it from history. +Clean unpublished commits before pushing; do not merge a development branch +whose history contains private documents into a public branch. + Configuration and task fixtures must use generic placeholders. Absolute personal paths, internal DNS names, private registry references, and unexplained large files will fail review. diff --git a/NOTICE.md b/NOTICE.md index 4dc4f09ac..3ab8ecf6f 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -2,6 +2,10 @@ HarborRL is MIT-licensed by Princeton-AI. +The local web console bundles Lucide 1.61.0 (ISC license). Its unmodified browser +distribution and license are in `harborrl/webui/static/lucide.min.js` and +`harborrl/webui/static/lucide-LICENSE.txt`. No CDN connection is required. + The public package does not vendor Slime, Megatron-LM, SGLang, Harbor, Claude Code, or model weights. Operators are responsible for reviewing the licenses of the exact external versions they pin and for complying with model and dataset diff --git a/README.md b/README.md index cc82cddd7..1b1b8ac7d 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ HarborRL

- +

A lightweight agentic reinforcement learning framework for everyone @@ -25,6 +25,8 @@ ## News +- **2026-10-26** Added an [offline configuration wizard](docs/configuration-wizard.md) and a [local Web console](docs/web-console.md) for configuration, validation, dry-runs, training launch, and logs. +- **2026-10-26** Added [controller/GPU deployment configuration](docs/deployment.md), optional [OpenSandbox execution](docs/sandbox-rollout.md), and opt-in [train/inference consistency checks](docs/train-inference-consistency.md). - **10/2026** 📣📣📣 HarborRL released ## About @@ -40,6 +42,14 @@ HarborRL supports flexible combinations of **Models × Algorithms × Tasks × Ha ## AI-assisted Configure +For the offline options catalog, interactive configuration, and answer-file setup, +see the [configuration CLI guide](docs/configuration-wizard.md). +Start with `harborrl options` or `harborrl init --help`. + +Run `harborrl web` to open the local browser console for configuration, validation, +dry-runs, training launch, and logs. See the [web console guide](docs/web-console.md) +for remote-controller access and lifecycle details. + If you want an AI agent to help deploy this repository and verify training, ask it to first read and strictly follow [docs/agents/environment-check.md](docs/agents/environment-check.md). @@ -78,6 +88,12 @@ Confirm the result yourself against this checklist: ## Quick Start +The default CPU install does not need a Sandbox SDK or cluster account. +For controller placement, see [deployment](docs/deployment.md). For optional +standard OpenSandbox execution, see [Sandbox rollout](docs/sandbox-rollout.md). +The independently installed [H-cluster practice](practices/h_cluster/README.md) +contains Brainbox-specific deployment and restricted-network relay examples. + ### Install Use Python 3.10 or newer. Runtime dependencies, development dependencies, and the `harborrl` command are locked by [pyproject.toml](pyproject.toml) and [uv.lock](uv.lock). The CPU development install does not require CUDA, Docker, model weights, Slime, Megatron-LM, or SGLang. @@ -128,7 +144,7 @@ The command style is a shorthand over `config.yaml` in the working directory (or ## Task Configuration -Training is configured with one YAML file that has a fixed section and field set. Relative paths resolve against the YAML file, and repeated `--set section.field=value` overrides are parsed as YAML values and validated again. Start site-specific configurations from [examples/native/train_qwen_native.yaml](examples/native/train_qwen_native.yaml). +Training is configured with one YAML file that has a fixed section and field set. Relative paths resolve against the YAML file, and repeated `--set section.field=value` overrides are parsed as YAML values and validated again. Start site-specific configurations from [examples/native/train_qwen_native.yaml](examples/native/train_qwen_native.yaml). Optional [train/inference consistency checks](docs/train-inference-consistency.md) compare frozen rollout scores with the pre-update trainer and probe synchronized inference engines. The configuration has three broad parts: `execution` is fixed to `harbor_job`; `harbor`, `gateway`, and `deployment` describe rollout workers, service endpoints, and GPU topology; `tasks`, `harness`, `model`, `training`, `sampling`, and `output` describe the experiment and its outputs. diff --git a/README_zh.md b/README_zh.md index 18a33b25c..f75e744a9 100644 --- a/README_zh.md +++ b/README_zh.md @@ -25,6 +25,8 @@ ## News +- **2026-10-26** 新增[离线配置向导](docs/configuration-wizard.md)和[本地 Web 控制台](docs/web-console.md),支持配置、校验、dry-run、训练启动和日志查看。 +- **2026-10-26** 新增 [controller/GPU 部署配置](docs/deployment.md)、可选的 [OpenSandbox 执行能力](docs/sandbox-rollout.md),以及按需启用的[训练与推理一致性检查](docs/train-inference-consistency.md)。 - **10/2026** 📣📣📣 HarborRL released ## 项目介绍 @@ -43,6 +45,11 @@ HarborRL 支持 **Models × Algorithms × Tasks × Harnesses (MATH)** 的灵活 如果你希望让 AI agent 辅助完成本 repo 的部署和训练验证,请让它先阅读并严格遵循 [docs/agents/environment-check.md](docs/agents/environment-check.md)。 +离线选项目录、交互式配置和 answer-file 配置方式见[配置 CLI 指南](docs/configuration-wizard.md)。 +可以从 `harborrl options` 或 `harborrl init --help` 开始。 +运行 `harborrl web` 可打开本地浏览器控制台,完成配置、校验、dry-run、训练启动和日志查看; +远程访问与生命周期说明见 [Web 控制台指南](docs/web-console.md)。 + 推荐的实际操作流程是把下面这段提示词交给 agent,并把尖括号内容替换成你的机器信息: ```text @@ -73,6 +80,10 @@ doctor 和 launch_native --dry-run。 ## 快速开始 +默认 CPU 安装不需要 Sandbox SDK 或集群账号。 +controller 与 GPU 的部署方式见[部署指南](docs/deployment.md),可选的标准 OpenSandbox 执行方式见[Sandbox rollout 指南](docs/sandbox-rollout.md)。 +独立安装的 [H-cluster 实践包](practices/h_cluster/README.md)提供 Brainbox 部署与受限网络中继示例。 + ### 安装 使用 Python 3.10 或更新版本。Python 依赖、开发依赖和 `harborrl` 命令由 [pyproject.toml](pyproject.toml) 与 [uv.lock](uv.lock) 锁定。CPU 开发环境不需要 CUDA、Docker、模型权重、Slime、Megatron-LM 或 SGLang。 diff --git a/docs/configuration-wizard.md b/docs/configuration-wizard.md new file mode 100644 index 000000000..441a6138b --- /dev/null +++ b/docs/configuration-wizard.md @@ -0,0 +1,140 @@ +# Configuration CLI + +The configuration tools run with the core package (Python 3.10+ and PyYAML). +They enumerate implemented capabilities, check configuration offline, and create +consistent training schema 2 / deployment schema 1 files. They do not install +software, download tasks or models, connect over SSH, or allocate Sandbox/GPU jobs. + +## Browse and validate + +```bash +harborrl options +harborrl options deployments --json +harborrl options fields --json +harborrl validate site/config.yaml --deployment site/deployment.yaml --json +harborrl validate site/config.yaml --deployment site/deployment.yaml --check-files +``` + +Catalog dimensions are `models`, `algorithms`, `tasks` (task providers), +`harnesses`, `datasets`, `deployments`, `fields`, and `compat`. JSON keeps the +`catalog_version` and `schema_versions` envelope when selecting a dimension. +The catalog reflects registered implementations, not roadmap claims. Dataset +`status` describes support; `available` describes local task files. A wheel does +not include repository examples, and planned datasets are not ready to train. + +Validation defaults to schema, semantics, and cross-file consistency. It does +not open referenced profiles or tasks until `--check-files` is supplied. Run file +checks on the controller: its source/interpreter, profile, task catalog and +Sandbox configuration belong there. Local GPU and runner paths are checked +locally; SSH target paths are deferred. The shared root may be created by a later +run and is not required to exist during configuration. Credentials are never read. + +Checks are `passed`, `failed`, or `deferred`. The report is `invalid` if a check +fails, otherwise `valid_with_deferred_checks` while live checks remain. Exit codes +are 0 for no failed checks, 1 for validation/creation failure, and 2 for CLI usage +errors. `--json` writes one JSON object to stdout. Schema validators may report +only their first error. Without `--deployment`, the existing CLI discovery of +`./deployment.yaml` and embedded fallback is preserved and reported. + +GPU memory fit, model loading, live tokenizer fingerprints, package versions, +connectivity and platform capacity are not established by offline validation. +Sandbox image bindings are checked locally; trusted site adapters and credentials +are checked later by runner preflight. Sandbox installation/profile timing +compatibility is additionally checked by the existing training dry-run. + +## Prepare inputs + +1. Obtain Harbor-format tasks and run `harborrl hub inspect --tasks-dir /path/tasks + --dataset my-tasks --revision pinned-revision --run-dir /path/inspection`. + The resulting JSONL is an inspection report, not a training catalog. Build a + JSON list (or JSONL records with a `.jsonl` suffix) containing exactly `id`, + `revision`, `path`, `task_digest`, and `reward_profile` for each selected task. + Copy `path` and `task_digest` from a `NEEDS_PROBE` inspection record, assign a + unique stable `id`, retain the pinned revision, and explicitly set the verifier's + reward contract (for example `reward_profile: {key: reward}`). See + [the catalog example](../examples/native/catalog.jsonl). `INVALID` and + `UNSUPPORTED` inspection results cannot be made trainable by changing labels. + Do not replace computed digests with placeholder values. +2. Prepare a pinned Claude Code profile. See + [the native examples](../examples/native) for profile structure. It must match + the agent and task requirements of your deployment. +3. Stage checkpoint and reference directories on the GPU target. In its existing + training environment, load the actual local tokenizer with the same options as + the runtime and call `harborrl.rollout.runtime.tokenizer_fingerprint(tokenizer)`. + Its return values are `tokenizer_digest, template_digest`. This is an explicit + preparation step, not an operation performed by the wizard. Hashing a filename + or guessing an arbitrary 64-character value does not establish compatibility. +4. Identify controller/GPU/runner Python executables, checkout paths, SSH aliases + and shared storage. Service origins must include their declared ports and be + reachable by the participating machines. +5. For Sandbox, prepare the existing platform configuration and image bindings. + H-cluster users follow [the optional H practice](../practices/h_cluster/README.md) + and reference its site file. The core CLI never asks for H project/environment + identifiers or embeds private endpoints or credentials. + +## Generate + +```bash +harborrl init --out-dir ./site-new +harborrl init --answers my-answers.yaml --out-dir ./site-new +``` + +Interactive mode asks for deployment roles, existing inputs, model fingerprints, +GPU layout and a few sampling settings, then previews both configurations and +requires `yes` to publish. Entering no or EOF cancels. Non-terminal use requires +`--answers`; missing required answers never fall back to prompting. + +Start from one of these complete answer templates and replace every `REPLACE_*` +value and example path before running: + +- [embedded-local](../examples/options/embedded-local.answers.yaml): one machine, + local Harbor runner, colocated training/serving by default. +- [service-ssh](../examples/options/service-ssh.answers.yaml): controller, SSH GPU + target and explicit SSH Docker workers. +- [service-sandbox-local-runner](../examples/options/service-sandbox.answers.yaml): + controller and runner here, SSH GPU target, Sandbox task execution. + +Templates deliberately contain invalid digest placeholders. They cannot claim +training readiness without your real inputs. Default one-GPU colocate is a +configuration starting point, not a Qwen model memory-fit recommendation. + +Answers allow only `answers_version: 1`, `preset`, `training`, `deployment`, and +optional `sandbox_config`. Overrides use the existing schema section/field names; +unknown keys fail. No expressions or shell commands are evaluated. Gateway fields, +shared run root, GPU budget and worker destinations are derived when only one +side is provided. Contradictory explicit values fail. Supply matching origin URLs +when changing ports. Paths in training overrides are relative to the **final +output directory**, not the answers file. Deployment machine paths are absolute. + +The Sandbox preset can also describe remote runners through an answers file: +set `training.harbor.runner_transport: ssh` and provide deployment rollout workers +with `docker: false`. The builder derives training worker destinations and the +first worker's runner Python. All configured interpreters still require preflight. + +`--probe-local` explicitly enables bounded local `nvidia-smi` UUID and current +Python version queries. It also reads explicit SSH Host aliases (no wildcard, +Match execution or Include expansion) and gives a parent-directory access hint. +Missing tools and failed commands return `unknown`, not zero GPUs. Suggestions +never silently change answers; no remote probe or write permission test occurs. + +The output directory must not exist, and its parent must exist. Publication uses +an exclusive per-target lock, same-parent staging and a directory rename. Existing +files are never overwritten. Failures clean up only this invocation's staging and +lock. After an abrupt process termination, inspect any remaining `.NAME.init.lock` +and staging directory before removing them; automatic stale-lock recovery is not +attempted. The lock coordinates wizard processes, not arbitrary external writers. + +The bundle contains `config.yaml`, `deployment.yaml`, and `setup-manifest.json`. +The manifest records schema versions, configuration digests, references and the +offline validation scope, including deferred checks. It does not copy credentials. + +The CLI prints these next commands without executing them: + +```bash +harborrl validate site-new/config.yaml --deployment site-new/deployment.yaml --check-files +harborrl train site-new/config.yaml --deployment site-new/deployment.yaml --dry-run +harborrl doctor site-new/config.yaml --deployment site-new/deployment.yaml +``` + +`doctor` performs live environment/connectivity checks. Actual training remains a +separate action. Command examples use POSIX shell quoting. diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 000000000..2f35a8501 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,110 @@ +# Deployment configuration and the controller placement + +A `deployment.yaml` +describes where the manager, GPU training target and rollout workers live and +how they reach each other; the training YAML keeps every training-side field +(learning rate, sampling, task catalog) and is unchanged. + +```bash +harborrl train train.yaml --deployment deployment.yaml +``` + +Without `--deployment`, a `deployment.yaml` in the working directory is picked +up; when no file exists the compatible embedded-local topology is used and the +legacy same-machine behavior is preserved bit-for-bit (recorded in the plan as +`deployment.source: embedded-local`). + +## What the schema covers (v1) + +- `manager` — the HarborRL controller node: `embedded` or `service` mode, bind + address, control/gateway ports and the URLs other machines use to reach them. + In `service` mode both URLs must be plain origins that match their ports and + must not be loopback. +- `gpu_target` — the Slime training machine: `local` or `ssh` transport, the + checkout (`source_dir`), training interpreter (`train_python`), an optional + controlled `environment_file` (a JSON variable map, never a shell), and + `expected_gpus`. +- `storage` — `shared_fs` mode with the shared `run_root` and writability + expectations per role. +- `rollout` — `ssh_docker` workers: SSH destination, runner interpreter, + Docker requirement and task roots. The `embedded` provider keeps worker + discovery in the training config. +- `connectivity` — timeouts and which control-loop hops are required. + +Secrets are rejected by the schema: no passwords, keys or API tokens. SSH +destinations resolve through the invoking user's SSH config, agent or a +restricted identity file. Paths are interpreted in target-machine semantics — +the manager never assumes its own `sys.executable` works on the GPU or workers. +See `examples/native/deployment-embedded.yaml` and +`examples/native/deployment-service.yaml`. + +## Preflight graph + +`train` and `doctor` share one check graph, executed at the earliest launch +stage. Each check records the role and machine it ran against, the outcome, +timing, and a user-executable fix. Any failed check exits nonzero before a run +directory or training process is created. + +Layer order: manager local state (run root, token file, ports) → GPU target +(SSH reachability, paths, model paths, visible GPU budget) → rollout workers +(SSH, runner Python >= 3.12, pinned Harbor version, Docker, task roots) → +control-loop addresses (DNS resolution now, live health dials after components +start). `--dry-run` parses both configs and prints the resolved plan without +any remote probing or side effects. + +When the GPU target is remote, machine-local dependency probes (Ray, Torch, +Slime imports, SGLang image, `nvidia-smi`) do not run on the manager: they are +enforced fail-closed by `slime_train.sh` at GPU job startup. The manager CPU +needs no GPU dependencies. + +## TrainingTarget + +`harborrl/deployment/training_target.py` launches one Slime job either as a +local child (legacy path) or over SSH (service mode). The SSH target writes a +generated `gpu-launcher.sh` into the shared run directory, starts it detached +(`setsid`) on the GPU machine, records the launcher pid, polls the shared +`exit-code` record, and cancels by signalling the remote process group +(INT → TERM → KILL). It never resubmits or restarts a job. + +## RolloutService (manager mode) + +`harborrl/rollout/service.py` is the manager-side control plane: HTTP JSON +with run-scoped tokens. + +- `GET /healthz`, `GET /readyz` +- `POST /runs/{run_id}/register` — idempotent by manifest digest; the same id + with a different manifest is a 409. +- `POST /runs/{run_id}/groups` — idempotent group submission. Same group id + and content returns the existing state; same id with different content is a + 409; submitting against a non-admitted policy version is a 409. On timeout + the response says `running` and points at the status endpoint — the client + must query, never resubmit. +- `GET /runs/{run_id}/groups/{group_id}`, `POST .../cancel` +- `POST /runs/{run_id}/policy/activate` — validates the whole serving pool is + healthy on the expected version, drains old admission, then opens the new + version and publishes `policy-pool.json` into the shared run directory. +- `POST /runs/{run_id}/serving/announce` — the GPU job publishes its inference + endpoint; the manager never hand-configures it. +- `POST /runs/{run_id}/drain`, `POST /runs/{run_id}/cancel` + +Bulky data (trajectory manifests, token ids, logprobs, loss masks, group +export) is written to the shared run directory. HTTP responses carry paths +plus SHA-256 digests; `GroupResult` also carries per-slot identity, finish +reason, reward, trace seal status and verifier digest. + +The GPU-side adapter (`harborrl/backends/slime_v032/rollout_service_client.py`) +recomputes every manifest digest, checks identities against the request, +requires a complete group of at least two slots on the locked policy version, +and only then converts trajectories into Slime samples. It is selected by +`HARBORRL_ROLLOUT_SERVICE_URL` in the GPU job environment. + +## Operational limits + +Service mode requires shared storage visible at the configured paths on the +controller, GPU target and runners. A successful dry run checks configuration; +it does not establish network reachability, available capacity or training +quality. Check connectivity with doctor and inspect run artifacts after launch. + +Choose worker and group concurrency for the target deployment. Instance +readiness does not imply that an agent is running, and a completed checkpoint +does not establish learning improvement. diff --git a/docs/sandbox-rollout.md b/docs/sandbox-rollout.md new file mode 100644 index 000000000..6fdc9d19a --- /dev/null +++ b/docs/sandbox-rollout.md @@ -0,0 +1,72 @@ +# Sandbox rollout + +Sandbox is an optional execution environment under the common Controller, +RolloutService, Runtime and Harbor Runner. Docker execution remains the default. +The core does not import the SDK until an explicitly selected runner needs it. + +## Standard OpenSandbox + +Use Python 3.12 or newer for the runner and install `pip install -e '.[sandbox]'`. +Start with `examples/native/sandbox.yaml`. Set the service origin (without a +trailing /v1), a private credential-file reference and exact task image bindings. +Standard credentials are opaque tokens, not a platform-specific AK:SK format. + +Select the environment in your training config: + +```yaml +harbor: + python: /opt/harborrl/runner/bin/python + version: 0.23.0 + runner_transport: local + workers: [] + environment: + runtime: sandbox + config: /srv/harborrl/site/sandbox.yaml +``` + +For service placement, use `rollout.provider: sandbox` in the deployment file. +A local runner requires an empty deployment worker list. SSH runners require +explicit workers, the same shared task/evidence paths and the adapter installed +on each runner. Runner placement is independent of the environment provider. +The target and route must be validated before claiming that an SSH/Sandbox +deployment works on a real platform. + +The standard adapter uses Harbor's OpenSandbox implementation and adds durable +allocation tracking, bounded lifecycle operations and cleanup-debt records. +Custom trusted adapters subclass ManagedOpenSandboxEnvironment and are selected +through `adapter: module:Class`; arbitrary task input cannot select code. +Provider settings belong in `options` and per-image binding `options`. +They must not contain inline secrets. Core plan construction does not import +the adapter, read credentials, connect to a platform or allocate instances. + +The optional `installation` block supports a pinned offline Claude binary: +`mode: offline_binary`, `version`, `binary`, and its 64-character `sha256`. +Without it, Harbor performs normal agent setup, which can need network access. + +## Concurrency and evidence + +`max_live_instances` bounds all active instances; each image binding has its +own limit. Binding capacity is acquired before global capacity to avoid +starvation. `startup_concurrency` separately bounds simultaneous SDK startup +using process-shared locks. Shared-filesystem flock support is required when +runners use the same lock directory across hosts. + +Groups use the same identity, policy barrier, verifier and consumption audit +as Docker. Failed slots cancel their group siblings; submitted independent +groups finish before a partial batch is rejected. Unconfirmed cleanup blocks +new admission. Production export never accepts synthetic evidence. + +## Checks + +`python scripts/sandbox_sdk_contract.py` checks installed APIs and fake lifecycle +cleanup without contacting a platform. `sandbox_probe` checks the selected +adapter, SDK, credential-file permissions and optional binary locally. +Its explicit `--network` mode is adapter-specific and reports skipped checks. + +`python scripts/sandbox_smoke.py CONFIG --output NEW_DIRECTORY --gateway URL` +is a live allocation test and consumes platform resources. A successful CPU +test or dependency probe does not establish live training acceptance. + +For Brainbox and developer-mediated networking, see the independently +installable [H-cluster practice](../practices/h_cluster/README.md). H-specific +configuration from the experimental branch must be migrated as described there. diff --git a/docs/train-inference-consistency.md b/docs/train-inference-consistency.md new file mode 100644 index 000000000..f4bd4d2d2 --- /dev/null +++ b/docs/train-inference-consistency.md @@ -0,0 +1,79 @@ +# Train / inference consistency + +Consistency checks compare log probabilities of the **same response tokens at +the same prefix**, before an optimizer update. This measures backend drift; +it does not measure model quality or full-distribution KL divergence. + +The three policies are the observed behavior `q`, frozen pre-update trainer +`p0`, and current trainer `pθ`. Backend drift is `log p0 - log q`; optimization +drift is `log pθ - log p0`. The Native loss remains unchanged. No TIS, clipping +correction, or new training objective is introduced. + +## Configuration + +Native schema 2 accepts an optional `consistency` mapping. Its default mode is +`off`. Existing configurations remain valid. `audit` records numerical violations; +`enforce` rejects violations before +training or serving admission. Invalid evidence always rejects, including in +audit mode. Enforce requires an explicit `max_abs_delta`. Calibrate tolerances +for the actual model, precision, parallel layout and kernels. + +```yaml +consistency: + mode: audit + max_abs_delta: null + max_mean_abs_delta: null + bad_token_delta: null + max_bad_fraction: 0.0 + worst_tokens: 8 +``` + +Native overrides use the existing `--set consistency.mode=audit` interface. +For enforce, configure a deployment-specific `max_abs_delta` in YAML before +loading the configuration. The configuration is preserved in launch evidence. + +## Native Megatron / SGLang + +The enabled launcher runs `harborrl.consistency.train`, which uses the pinned +Slime training loop and its supported actor factory. It never edits upstream +Slime. The existing pre-update forward supplies actual packed-batch `p0` values. +Immutable serving sidecars bind token IDs, float32-normalized behavior scores, +response masks, policy version, tokenizer and template identity. Missing or +mismatched sidecars reject the batch. + +Before each weight synchronization, the actor evaluates two fixed token +fixtures using Megatron. Every synchronized SGLang engine then teacher-forces +those exact sequences. Generation-time and post-probe versions must both match. +Each engine must satisfy the numerical limits independently. Checks also cover +the final sync, even when no subsequent rollout occurs. Probe records are reused +only for the same pool, configuration and immutable reference. A fixed canary +is a smoke check; actual rollout-batch checks provide broader coverage. + +Supported initial scope: dense models, synchronous full NCCL synchronization, +PP=CP=1, with TP and DP. Temperature and top-p must be 1; top-k must be disabled. +Critic, old-actor queues, MoE, release-train and routing replay are rejected. +Transformed sampling would require a separate definition of behavior scores. + +`RUN_DIR/consistency` contains sync references, pool admissions, serving-turn +sidecars and `batch-*.json` reports. References include a bounded sampled +parameter fingerprint per rank; this is **not** a full weight checksum. Reports +contain valid-token-weighted absolute/signed errors, log-ratio range, bad-token +fraction, bounded worst positions and histogram percentile **upper bounds**. +They do not copy raw prompt or response text. TP duplicate counts are excluded. +Distributed validation collects rank errors before all ranks reject. + +## Validation and limitations + +Run `pytest -q tests/test_consistency.py` for the dependency-free checks. With +Torch installed, run `pytest -q tests/test_consistency_torch.py` for two-rank +failure propagation. These tests do not run a separate training loop. + +GPU acceptance must additionally exercise the locked Megatron / SGLang versions +on the target hardware: initial sync, actual rollout tokens, optimizer update, +final sync and checkpoint resume. Record dtype, attention backend, TP/DP, model +revision and measured error distribution. CPU tests and mocked HTTP responses +cannot certify SGLang input-logprob behavior or CUDA numerical tolerances. +This implementation has not yet passed that live GPU acceptance gate. + +H-cluster relay topology remains a deployment practice. No H-cluster hostnames, +private paths or machine-specific numerical thresholds belong in this module. diff --git a/docs/web-console.md b/docs/web-console.md new file mode 100644 index 000000000..3d9603ccc --- /dev/null +++ b/docs/web-console.md @@ -0,0 +1,110 @@ +# Local web console + +```bash +harborrl web --workspace ./harborrl-web +``` + +This opens a browser on an available loopback port. Keep the terminal running. +`--port 8765` chooses a fixed port; `--no-browser` prints the URL without opening +it. The workspace stores saved configuration bundles and execution records. +One console process owns a workspace at a time. The web console uses the core +Python dependencies; Node, a frontend build and CDN access are not required. + +## Configure and launch + +1. Select Local, SSH workers, or Sandbox. Fill controller/GPU/runner paths and + service origins. Source and interpreter paths refer to the selected machine. + The controller source directory and Python executable select the checkout and + environment used by the launched CLI. +2. Provide existing checkpoint/reference directories, a pinned task catalog, + harness profile and real tokenizer/template fingerprints. The + [configuration CLI guide](configuration-wizard.md) describes preparing these + inputs. The console never downloads or generates model inputs. +3. Choose GPU layout, tensor parallelism, rollout batches and sampling settings. + Allocation defaults are configuration values, not a model memory-fit estimate. +4. Review the generated YAML and validation. **Check local files** additionally + checks references on the controller. Deferred GPU, connectivity, model and + Sandbox checks remain explicit. Save the bundle under a new configuration name. +5. Run **Dry-run** to build the actual CLI launch plan. **Doctor** performs live + checks after confirmation. **Start training** requires a separate confirmation + and invokes the existing `harborrl train` flow, including its preflight. + +Configuration names are limited to letters, digits, underscores and hyphens. +Saved configurations are not overwritten. Reopen one to launch it again, or edit +and save under a new name. The advanced answers editor and YAML/JSON import use +the existing `answers_version: 1` contract. Download an individual generated file +or export answers from the editor. Advanced remote Sandbox runners can be +specified through imported answers; the form preset defaults to a local runner. + +H-cluster deployments remain the optional +[H practice](../practices/h_cluster/README.md). Prepare its Sandbox configuration +separately and reference the path. H project/environment identifiers and relay +settings are not part of the core web form. + +Windows supports configuration and dry-run. Live training/doctor controls require +the console to run on a POSIX controller, using the same environment and checkout +that would normally run the training CLI. Deployment readiness still depends on +the project's supported Linux training environment. + +## Runs and shutdown + +Only one execution is active per workspace, including doctor and dry-run. This +prevents duplicate launches within this console; it is not a cluster-wide GPU +scheduler. Other CLI processes or different workspaces can still use the same +resources, and operators must coordinate those allocations. + +Each job keeps immutable input files and a durable `job.json`/`console.log` under +`WORKSPACE/jobs/JOB_ID`. Execution appends `web/JOB_ID` to the configured shared +run root and updates both config files in its snapshot. The original saved bundle +is unchanged. The existing runtime then creates its normal `training/...` run +directory there. This isolates artifacts while retaining the configured shared +filesystem. Run logs show the actual snapshot and output paths. + +The Runs view polls status and the last 64 KiB of output. **Controller** shows the +CLI output and errors. **GPU training** reads the separate `train-stdout.log` when +the service deployment creates one. Embedded training generally streams into +the controller log. Full logs remain on disk; the browser download contains the +currently displayed tail. This initial release does not aggregate WandB curves +or claim remote/GPU progress from a quiet controller log. + +**Stop** sends an interrupt through the existing CLI lifecycle. Local training +forwards it to the training process group; service mode uses the existing SSH +target cancellation. `stopping` means cleanup is pending, not that resources are +already free. Closing the browser does not stop a job. Ctrl+C or SIGTERM in the +server terminal requests cleanup; shutdown waits up to 30 seconds. A timeout +records an unknown state without force-killing arbitrary remote processes. + +After a crash or interrupted shutdown, previously running jobs appear as +`unknown`. New launches are blocked. Inspect the target processes, shared run +records and Sandbox cleanup evidence, then use **Confirm cleanup** to acknowledge +that external cleanup is complete. This action records operator confirmation; it +does not kill a stored PID or automatically relaunch a job. The server never +signals a PID recovered from an old session. + +## Remote controller + +On the controller, from the intended checkout/environment: + +```bash +python -m harborrl.cli web --workspace /srv/shared/web-console --port 8765 --no-browser +``` + +On the workstation, create a local SSH forward: + +```bash +ssh -N -L 127.0.0.1:8765:127.0.0.1:8765 your-controller-alias +``` + +Open the URL printed by the controller in the workstation browser. If the local +forward uses a different port, replace only the URL port. Leave the `#token=...` +fragment intact. If either port is occupied, choose an unused port; omitting +`--port` selects one automatically and prints it. + +The listener is deliberately fixed to `127.0.0.1`. A random per-session token +protects API reads and writes; the browser moves the URL fragment into +sessionStorage and removes it from the address bar. State-changing requests also +require a same-origin JSON request. Responses use CSP, frame blocking and +no-store headers, and assets are packaged locally. Keep the token private: this +is a local single-user console with the controller user's filesystem and training +privileges. It is not a public multi-user service or an Internet-facing gateway. +Do not expose it through a public reverse proxy. diff --git a/examples/native/catalog.jsonl b/examples/native/catalog.jsonl new file mode 100644 index 000000000..eba16a31c --- /dev/null +++ b/examples/native/catalog.jsonl @@ -0,0 +1 @@ +{"id": "hello_world", "path": "tasks/hello_world", "revision": "v0.1-example", "reward_profile": {"key": "reward", "offset": 0.0, "raw_range": [0.0, 1.0], "scale": 1.0}, "task_digest": "905f496d2852cca5b1e86e9de72395b41c291d2b8f6a80807e0ed24ca19541b3"} diff --git a/examples/native/deployment-embedded.yaml b/examples/native/deployment-embedded.yaml new file mode 100644 index 000000000..12c5cef81 --- /dev/null +++ b/examples/native/deployment-embedded.yaml @@ -0,0 +1,45 @@ +# Embedded (same-machine) deployment: identical to the legacy pipeline. +# Use this file explicitly, or simply omit --deployment to get the same +# embedded-local topology built from the training config. +schema_version: 1 + +manager: + id: embedded-local + mode: embedded + bind: 127.0.0.1 + control_port: 49200 + control_url: http://127.0.0.1:49200 + gateway_port: 49184 + gateway_url: http://127.0.0.1:49184 + run_token_file: /run/secrets/harborrl/run-token + source_dir: /opt/harborrl/source + controller_python: /opt/harborrl/controller/bin/python + +gpu_target: + id: local + transport: local + ssh: null + source_dir: /opt/harborrl/source + train_python: /opt/harborrl/training/bin/python + environment_file: null + expected_gpus: 8 + +storage: + mode: shared_fs + run_root: /mnt/shared/harborrl/runs + manager_writable: true + gpu_writable: true + worker_writable: true + +rollout: + # The embedded provider keeps worker discovery in the training config. + provider: embedded + workers: [] + +connectivity: + command_timeout_s: 30 + ssh_timeout_s: 15 + http_timeout_s: 10 + gpu_to_controller: true + worker_to_gateway: true + manager_to_gpu_inference: true diff --git a/examples/native/deployment-service.yaml b/examples/native/deployment-service.yaml new file mode 100644 index 000000000..b3584f5f9 --- /dev/null +++ b/examples/native/deployment-service.yaml @@ -0,0 +1,43 @@ +# Replace example hosts and paths; no site-specific addresses are required. +schema_version: 1 +manager: + id: manager + mode: service + bind: 0.0.0.0 + control_port: 49200 + control_url: http://manager.example:49200 + gateway_port: 49184 + gateway_url: http://manager.example:49184 + run_token_file: /srv/harborrl/site/run-token + source_dir: /srv/harborrl/repo + controller_python: /opt/harborrl/controller/bin/python +gpu_target: + id: trainer + transport: ssh + ssh: {host: training-host} + source_dir: /srv/harborrl/repo + train_python: /opt/harborrl/training/bin/python + environment_file: /srv/harborrl/site/gpu-env.json + expected_gpus: 8 +storage: + mode: shared_fs + run_root: /srv/harborrl/runs + manager_writable: true + gpu_writable: true + worker_writable: true +rollout: + provider: ssh_docker + workers: + - id: worker-a + ssh: {host: rollout-host} + source_dir: /srv/harborrl/repo + runner_python: /opt/harborrl/runner/bin/python + docker: true + task_roots: [/srv/harborrl/tasks] +connectivity: + command_timeout_s: 30 + ssh_timeout_s: 15 + http_timeout_s: 10 + gpu_to_controller: true + worker_to_gateway: true + manager_to_gpu_inference: true diff --git a/examples/native/sandbox.yaml b/examples/native/sandbox.yaml new file mode 100644 index 000000000..4ab3788a5 --- /dev/null +++ b/examples/native/sandbox.yaml @@ -0,0 +1,10 @@ +# Standard OpenSandbox; install harborrl[sandbox] in a Python 3.12+ runner. +domain: https://sandbox.example +credential_file: /run/secrets/harborrl/sandbox-key +max_live_instances: 2 +startup_concurrency: 1 +bindings: + - image: registry.example/project/task:locked-version + max_live_instances: 2 +# Omit installation to use normal Harbor agent setup. Offline deployments +# may supply mode, version, binary and sha256; see docs/sandbox-rollout.md. diff --git a/examples/options/embedded-local.answers.yaml b/examples/options/embedded-local.answers.yaml new file mode 100644 index 000000000..7a4bc5db1 --- /dev/null +++ b/examples/options/embedded-local.answers.yaml @@ -0,0 +1,30 @@ +# Replace every example path and REPLACE_* value. No model fit is implied. +answers_version: 1 +preset: embedded-local +training: + tasks: + catalog: /srv/harborrl/inputs/tasks.jsonl + harness: + profile: /srv/harborrl/inputs/claude-code.json + harbor: + python: /opt/harbor/bin/python + model: + checkpoint: /srv/models/qwen + reference: /srv/models/qwen-reference + args_file: qwen3-8B + gateway: + tokenizer_digest: REPLACE_WITH_ACTUAL_TOKENIZER_SHA256 + template_digest: REPLACE_WITH_ACTUAL_TEMPLATE_SHA256 + deployment: + layout: colocate + num_gpus: 1 + sampling: + group_size: 2 + groups_per_batch: 1 +deployment: + manager: + source_dir: /srv/harborrl/source + controller_python: /opt/controller/bin/python + gpu_target: + source_dir: /srv/harborrl/source + train_python: /opt/training/bin/python diff --git a/examples/options/service-sandbox.answers.yaml b/examples/options/service-sandbox.answers.yaml new file mode 100644 index 000000000..a55de6d2f --- /dev/null +++ b/examples/options/service-sandbox.answers.yaml @@ -0,0 +1,34 @@ +# Prepare a standard or practice-specific Sandbox config separately. +answers_version: 1 +preset: service-sandbox-local-runner +sandbox_config: /srv/shared/site/sandbox.yaml +training: + tasks: + catalog: /srv/shared/inputs/tasks.jsonl + harness: + profile: /srv/shared/inputs/claude-code.json + harbor: + python: /opt/sandbox-runner/bin/python + model: + checkpoint: /srv/models/qwen + reference: /srv/models/qwen-reference + args_file: qwen3-8B + gateway: + tokenizer_digest: REPLACE_WITH_ACTUAL_TOKENIZER_SHA256 + template_digest: REPLACE_WITH_ACTUAL_TEMPLATE_SHA256 + deployment: + layout: colocate + num_gpus: 1 +deployment: + manager: + source_dir: /srv/shared/harborrl + controller_python: /opt/controller/bin/python + control_url: http://controller.example:49200 + gateway_url: http://controller.example:49184 + gpu_target: + ssh: + host: gpu-training + source_dir: /srv/shared/harborrl + train_python: /opt/training/bin/python + storage: + run_root: /srv/shared/runs diff --git a/examples/options/service-ssh.answers.yaml b/examples/options/service-ssh.answers.yaml new file mode 100644 index 000000000..7a5851da4 --- /dev/null +++ b/examples/options/service-ssh.answers.yaml @@ -0,0 +1,44 @@ +# All paths refer to the named role's machine. Replace before use. +answers_version: 1 +preset: service-ssh +training: + tasks: + catalog: /srv/shared/inputs/tasks.jsonl + harness: + profile: /srv/shared/inputs/claude-code.json + model: + checkpoint: /srv/models/qwen + reference: /srv/models/qwen-reference + args_file: qwen3-8B + gateway: + tokenizer_digest: REPLACE_WITH_ACTUAL_TOKENIZER_SHA256 + template_digest: REPLACE_WITH_ACTUAL_TEMPLATE_SHA256 + deployment: + layout: split + num_gpus: 8 + actor_gpus: 4 + rollout_gpus: 4 + actor_tensor_parallel_size: 4 + rollout_gpus_per_engine: 4 +deployment: + manager: + source_dir: /srv/shared/harborrl + controller_python: /opt/controller/bin/python + control_url: http://controller.example:49200 + gateway_url: http://controller.example:49184 + gpu_target: + ssh: + host: gpu-training + source_dir: /srv/shared/harborrl + train_python: /opt/training/bin/python + storage: + run_root: /srv/shared/runs + rollout: + workers: + - id: runner-0 + ssh: + host: rollout-worker + source_dir: /srv/shared/harborrl + runner_python: /opt/harbor/bin/python + docker: true + task_roots: [/srv/shared/tasks] diff --git a/harborrl/algorithms/__init__.py b/harborrl/algorithms/__init__.py new file mode 100644 index 000000000..61b32100b --- /dev/null +++ b/harborrl/algorithms/__init__.py @@ -0,0 +1 @@ +"""Training algorithm adapters independent of backend hook plumbing.""" diff --git a/harborrl/algorithms/grpo.py b/harborrl/algorithms/grpo.py new file mode 100644 index 000000000..34e98d7dc --- /dev/null +++ b/harborrl/algorithms/grpo.py @@ -0,0 +1,18 @@ +"""GRPO semantics backed by the existing CPU-verified implementations.""" +from __future__ import annotations + +from harborrl.backends.slime_v032.advantage import compute_advantages_and_returns +from harborrl.backends.slime_v032.loss import clipped_loss, loss_function + + +class GRPOAlgorithmAdapter: + algorithm_id = "grpo" + + def compute_advantages_and_returns(self, args, rollout_data): + return compute_advantages_and_returns(args, rollout_data) + + def clipped_loss(self, *args, **kwargs): + return clipped_loss(*args, **kwargs) + + def backend_loss_function(self, *args, **kwargs): + return loss_function(*args, **kwargs) diff --git a/harborrl/backends/slime_v032/config.py b/harborrl/backends/slime_v032/config.py new file mode 100644 index 000000000..cc52b864d --- /dev/null +++ b/harborrl/backends/slime_v032/config.py @@ -0,0 +1,125 @@ +"""Schema-2 to official Slime launch mapping; no live probes.""" +import json +import os +import sys + +CONFIG_ENV = { + "HF_CKPT": ("model", "checkpoint"), + "REF_LOAD": ("model", "reference"), + "MODEL_ARGS_FILE": ("model", "args_file"), + "MODEL_TAG": ("model", "args_file"), + "NUM_ROLLOUT": ("training", "num_rollout"), + "SAVE_INTERVAL": ("training", "save_interval"), + "LR": ("training", "learning_rate"), + "ROLLOUT_BATCH_SIZE": ("sampling", "groups_per_batch"), + "N_SAMPLES": ("sampling", "group_size"), + "ROLLOUT_MAX_RESPONSE_LEN": ("sampling", "max_tokens"), + "ROLLOUT_MAX_CONTEXT_LEN": ("sampling", "max_context"), + "NUM_GPUS": ("deployment", "num_gpus"), + "ACTOR_GPUS": ("deployment", "actor_gpus"), + "ROLLOUT_GPUS": ("deployment", "rollout_gpus"), + "TP_SIZE": ("deployment", "actor_tensor_parallel_size"), + "ROLLOUT_NUM_GPUS_PER_ENGINE": ("deployment", "rollout_gpus_per_engine"), + "HARBORRL_GPU_LAYOUT": ("deployment", "layout"), + "HARBORRL_NATIVE_RUNNER_PYTHON": ("harbor", "python"), + "HARBORRL_NATIVE_PROFILE": ("harness", "profile"), + "HARBORRL_NATIVE_CATALOG": ("tasks", "catalog"), + "HARBORRL_NATIVE_GATEWAY_HOST": ("gateway", "host"), + "HARBORRL_NATIVE_GATEWAY_PORT": ("gateway", "port"), + "HARBORRL_NATIVE_GATEWAY_URL": ("gateway", "advertised_url"), + "HARBORRL_NATIVE_TOKENIZER_DIGEST": ("gateway", "tokenizer_digest"), + "HARBORRL_NATIVE_TEMPLATE_DIGEST": ("gateway", "template_digest"), + "HARBORRL_NATIVE_MAX_ATTEMPTS": ("sampling", "max_attempts"), + "RUNS_ROOT": ("output", "root"), +} + + +def launch_plan(config): + from harborrl.backends.slime_v032.versions import runtime_contract_id + from harborrl.config import ROOT + from harborrl.config.native import validate, validate_files + from harborrl.math_registry import select_math + from harborrl.tasks.catalog import catalog + validate(config, ROOT / "config.yaml") + validate_files(config) + selection = select_math(config) + entries = catalog(config) + runtime = config['harbor'].get('environment', {'runtime': 'docker'}) + sandbox = None + if runtime['runtime'] == 'sandbox': + from harborrl.rollout.harbor_job.sandbox_config import ( + load_sandbox, + task_binding, + ) + from harborrl.trajectories.native import read_json + sandbox = load_sandbox(runtime['config']) + profile = read_json(config['harness']['profile']) + if sandbox['installation'] and sandbox['installation']['version'] != profile['cli_version']: + raise ValueError('sandbox installation version differs from harness profile') + budget = sum(profile[k] for k in ('agent_timeout_sec', 'setup_timeout_sec', 'verifier_timeout_sec')) + if sandbox['lifecycle_minutes'] * 60 <= budget + sandbox['create_timeout_s'] + 2 * sandbox['cleanup_timeout_s']: + raise ValueError('sandbox lifecycle must cover setup, agent, verifier and cleanup budgets') + for entry in entries: + task_binding(sandbox, entry['path']) + c = config + worker_overrides = os.environ.get('HARBORRL_NATIVE_TASK_WORKER_OVERRIDES', '{}') + try: + overrides = json.loads(worker_overrides) + valid_workers = set(c['harbor']['workers']) + if (not isinstance(overrides, dict) or any(not isinstance(key, str) or not isinstance(value, str) or value not in valid_workers + for key, value in overrides.items())): + raise ValueError + except (ValueError, json.JSONDecodeError) as exc: + raise ValueError('invalid HARBORRL_NATIVE_TASK_WORKER_OVERRIDES') from exc + environment = { + **{name: c[section][field] for name, (section, field) in CONFIG_ENV.items()}, + 'DATASET': selection.task.id, 'ALGO': selection.algorithm.id, + 'HARNESS_OPTION': f"native-{selection.harness.id.replace('_', '-')}", + 'TRAIN_PYTHON': sys.executable, + 'HARBORRL_STRUCTURED_LAUNCH': '1', 'HARBORRL_VERIFY_POLICY_POOL': '1', + 'HARBORRL_SKIP_GLOBAL_CLEANUP': '1', 'SLIME_RAY_PLACEMENT_GPU_PROBE': '1', + 'PYTORCH_CUDA_ALLOC_CONF': ('max_split_size_mb:2048' if c['deployment']['layout'] == 'colocate' + else 'max_split_size_mb:2048,expandable_segments:True'), + 'HARBORRL_NATIVE_ROLLOUT': '1', + 'HARBORRL_NATIVE_SLIME_CONTRACT': runtime_contract_id(c['training']['backend_contract']), + 'HARBORRL_NATIVE_RUNNER_WORKERS': json.dumps(c['harbor']['workers']), + 'HARBORRL_NATIVE_TASK_WORKER_OVERRIDES': json.dumps(overrides, sort_keys=True), + 'HARBORRL_NATIVE_AUDITED_LOGPROBS': str(int(c['gateway']['audited_raw_logprobs'])), + 'HARBORRL_NATIVE_DRAIN_TIMEOUT': '30', + 'WANDB_ENABLE': '0', + 'HARBORRL_CONSISTENCY': json.dumps(c['consistency'], sort_keys=True), + 'HARBORRL_CONSISTENCY_MODE': c['consistency']['mode'], + } + environment['HARBORRL_NATIVE_RUNNER_TRANSPORT'] = c['harbor'].get('runner_transport', 'ssh') + if sandbox is not None: + environment['HARBORRL_NATIVE_SANDBOX'] = json.dumps(sandbox, sort_keys=True) + return {'config': config, 'math': selection.asdict(), + 'command': [sys.executable, '-m', 'harborrl.platform.native_train'], + 'environment': {key: str(value) for key, value in environment.items()}, + 'training_command': ['bash', str(ROOT / 'harborrl/platform/slime_train.sh')], + 'required_services': ['GPU Ray / SGLang / Messages Gateway', + 'Sandbox / Harbor runner' if sandbox else 'external CPU Harbor Docker workers'], + 'launcher': f"native {selection.harness.display_name} → Messages Gateway → " + f"{selection.task.display_name} verifier → " + f"{selection.algorithm.display_name}", + 'layout': {'mode': config['deployment']['layout'], 'offload': config['deployment']['layout']=='colocate'}} + + +def field_usage(): + """Account for every schema-2 field, including non-environment consumers.""" + usage = {} + from harborrl.consistency.config import DEFAULTS + usage.update({f'consistency.{key}': ['environment:HARBORRL_CONSISTENCY'] for key in DEFAULTS}) + for name, (section, field) in CONFIG_ENV.items(): + usage.setdefault(f"{section}.{field}", []).append(f"environment:{name}") + usage.update({ + "execution.backend": ["schema:harbor_job"], + "harness.name": ["schema:claude_code", "runner:Claude Code"], + "harbor.version": ["preflight:worker runtime version"], + "training.backend_contract": ["environment:HARBORRL_NATIVE_SLIME_CONTRACT", "preflight:backend hooks"], + "harbor.workers": ["environment:HARBORRL_NATIVE_RUNNER_WORKERS", "preflight:worker probe"], + "harbor.runner_transport": ["environment:HARBORRL_NATIVE_RUNNER_TRANSPORT"], + "harbor.environment": ["environment:HARBORRL_NATIVE_SANDBOX", "runner:environment"], + "gateway.audited_raw_logprobs": ["environment:HARBORRL_NATIVE_AUDITED_LOGPROBS"], + }) + return usage diff --git a/harborrl/backends/slime_v032/converter.py b/harborrl/backends/slime_v032/converter.py index 6098332b3..075a121ae 100644 --- a/harborrl/backends/slime_v032/converter.py +++ b/harborrl/backends/slime_v032/converter.py @@ -131,6 +131,8 @@ def convert_samples_to_train_data(args, samples): data["loss_masks"].append(response_mask) data["rollout_log_probs"].append(list(span.old_logprobs)) data["rollout_mask_sums"].append(unit.token_count) + from harborrl.consistency.slime import record_turn + record_turn(ir["turns"][turn_index], list(span.tokens), list(span.old_logprobs), response_mask) for group in batch.groups: weight_sum = math.fsum( 1.0 / (group.group_size * unit.token_count) diff --git a/harborrl/backends/slime_v032/launcher.py b/harborrl/backends/slime_v032/launcher.py index c0f5c9d0d..3450782d1 100644 --- a/harborrl/backends/slime_v032/launcher.py +++ b/harborrl/backends/slime_v032/launcher.py @@ -4,40 +4,37 @@ import sys from harborrl.backends.slime_v032.versions import BACKEND_CONTRACT +from harborrl.math_registry import MATH_REGISTRY -HOOK_PATHS = { - "rollout": "harborrl.backends.slime_v032.rollout.generate_rollout", - "converter": "harborrl.backends.slime_v032.converter.convert_samples_to_train_data", - "advantage": "harborrl.backends.slime_v032.advantage.compute_advantages_and_returns", - "loss": "harborrl.backends.slime_v032.loss.loss_function", - "postprocess": "harborrl.backends.slime_v032.postprocess.rollout_data_postprocess", -} - -def hook_arguments(contract=BACKEND_CONTRACT): +def _hooks(contract=BACKEND_CONTRACT): if str(contract) != BACKEND_CONTRACT: raise ValueError(f"unsupported backend contract: {contract!r}") + return MATH_REGISTRY.algorithm_for_contract(str(contract)).backend_hooks + + +def hook_arguments(contract=BACKEND_CONTRACT): + hooks = _hooks(contract) return [ - "--rollout-function-path", HOOK_PATHS["rollout"], - "--custom-convert-samples-to-train-data-path", HOOK_PATHS["converter"], - "--custom-advantage-function-path", HOOK_PATHS["advantage"], + "--rollout-function-path", hooks["rollout"], + "--custom-convert-samples-to-train-data-path", hooks["converter"], + "--custom-advantage-function-path", hooks["advantage"], "--loss-type", "custom_loss", - "--custom-loss-function-path", HOOK_PATHS["loss"], - "--rollout-data-postprocess-path", HOOK_PATHS["postprocess"], + "--custom-loss-function-path", hooks["loss"], + "--rollout-data-postprocess-path", hooks["postprocess"], ] def validate_hook_assignment(assignment, *, contract=BACKEND_CONTRACT): - if str(contract) != BACKEND_CONTRACT: - raise ValueError(f"unsupported backend contract: {contract!r}") - observed = {key: value for key, value in assignment.items() if key in HOOK_PATHS} - if observed != HOOK_PATHS: + hooks = _hooks(contract) + observed = {key: value for key, value in assignment.items() if key in hooks} + if observed != hooks: raise ValueError(f"Slime hook assignment must exactly match {BACKEND_CONTRACT}") return observed def hook_modules(): - return {name: path.rsplit(".", 1)[0] for name, path in HOOK_PATHS.items()} + return {name: path.rsplit(".", 1)[0] for name, path in _hooks().items()} def dry_run_command(entrypoint, base_args=(), *, contract=BACKEND_CONTRACT, python=sys.executable): diff --git a/harborrl/backends/slime_v032/loss.py b/harborrl/backends/slime_v032/loss.py index 72f386168..502c146eb 100644 --- a/harborrl/backends/slime_v032/loss.py +++ b/harborrl/backends/slime_v032/loss.py @@ -104,5 +104,12 @@ def loss_function(args, batch, logits, unused_reducer): "native_token_count": loss_masks.sum().detach(), "native_group_count": weight_sum.detach().round(), } + from harborrl.consistency.config import environment + if environment()["mode"] != "off": + p0 = torch.cat(batch["log_probs"]).detach() + current = torch.cat(output["log_probs"]).detach() + metrics["consistency_backend_abs_sum"] = ((p0 - old_logprobs).abs() * loss_masks).sum() + metrics["consistency_update_abs_sum"] = ((current - p0).abs() * loss_masks).sum() + metrics["consistency_valid_tokens"] = loss_masks.sum().detach() return loss * LOSS_SCALE_CORRECTION, metrics diff --git a/harborrl/backends/slime_v032/postprocess.py b/harborrl/backends/slime_v032/postprocess.py index d778412bc..7d4c89bb3 100644 --- a/harborrl/backends/slime_v032/postprocess.py +++ b/harborrl/backends/slime_v032/postprocess.py @@ -3,8 +3,6 @@ import math -from harborrl.trajectories.native import finite - REQUIRED_FIELDS = ( "tokens", "response_lengths", @@ -34,7 +32,7 @@ def _integer_scalar(value): return int(number) if math.isfinite(number) and number.is_integer() else None -def rollout_data_postprocess(args, rollout_id, rollout_data): +def _validate_rollout_data(args, rollout_id, rollout_data): """Slime ``--rollout-data-postprocess-path`` hook; fail closed on drift.""" missing = [field for field in REQUIRED_FIELDS if field not in rollout_data] if missing: @@ -51,8 +49,11 @@ def rollout_data_postprocess(args, rollout_id, rollout_data): group_size = int(getattr(args, "n_samples_per_prompt", 0)) if group_size < 2: raise ValueError("native training requires a complete group") - local_weight_sum = 0.0 + group_weights = {} for index in range(count): + group_id = _integer_scalar(rollout_data["rollout_ids"][index]) + if group_id is None or group_id < 0: + raise ValueError("invalid native rollout id") response_length = _integer_scalar(rollout_data["response_lengths"][index]) mask = rollout_data["loss_masks"][index] logprobs = rollout_data["rollout_log_probs"][index] @@ -71,8 +72,22 @@ def rollout_data_postprocess(args, rollout_id, rollout_data): raise ValueError("invalid native rollout mask sum") if truncated is None or truncated not in (0, 1): raise ValueError("invalid native truncated flag") - local_weight_sum += sum( - 1.0 / (group_size * mask_sum) for flag in mask if flag + group_weights.setdefault(group_id, []).append( + sum(1 for flag in mask if flag) / (group_size * mask_sum) ) - if not finite(local_weight_sum) or local_weight_sum > 1.0 + 1e-9: - raise ValueError(f"native local weights exceed the one-per-group budget: {local_weight_sum}") + # Actor shards may contain partial groups; each group has its own unit budget. + for group_id, weights in group_weights.items(): + local_weight_sum = math.fsum(weights) + if not math.isfinite(local_weight_sum) or local_weight_sum > 1.0 + 1e-9: + raise ValueError( + f"native local weights exceed the one-per-group budget for rollout id " + f"{group_id}: {local_weight_sum}" + ) + + +def rollout_data_postprocess(args, rollout_id, rollout_data): + from harborrl.consistency.config import environment + if environment()["mode"] == "off": + return _validate_rollout_data(args, rollout_id, rollout_data) + from harborrl.consistency.slime import check_training_batch + return check_training_batch(args, rollout_id, rollout_data, _validate_rollout_data) diff --git a/harborrl/backends/slime_v032/rollout.py b/harborrl/backends/slime_v032/rollout.py index c232921c2..44c36e2c8 100644 --- a/harborrl/backends/slime_v032/rollout.py +++ b/harborrl/backends/slime_v032/rollout.py @@ -1,11 +1,28 @@ -"""Slime v0.3.2 rollout function hook for complete Native Harbor groups.""" +"""Slime v0.3.2 rollout function hook for complete Native Harbor groups. + +This module is the training-side adapter: it maps Slime args and Sample +objects onto the framework-free rollout core. When the manager runs the +RolloutService (P3), groups are requested over HTTP instead of executing in +this process; conversion to Slime samples is identical either way. +""" from __future__ import annotations import asyncio import math +import os +from pathlib import Path from harborrl.backends.slime_v032.versions import lock_policy_version from harborrl.export.native import export_training_batch +from harborrl.rollout.runtime import ( + RolloutRuntimeConfig, + close_runtime, + current_policy_version, + digest, + run_group, + runtime, + task_entry, +) def sampling_params(args): @@ -21,6 +38,30 @@ def sampling_params(args): } +def runtime_config_from_args(args): + """Map Slime runtime args onto the extracted rollout core config.""" + endpoint = f"http://{args.sglang_router_ip}:{args.sglang_router_port}" + engine_count = int(args.rollout_num_gpus) // int(args.rollout_num_gpus_per_engine) + limit = max(1, int(args.sglang_server_concurrency) * engine_count) + return RolloutRuntimeConfig.from_environment( + os.environ, endpoint=endpoint, concurrency_limit=limit, + group_size=int(args.n_samples_per_prompt)) + + +def slime_tokenizer_loader(args): + """The single Slime dependency at the rollout boundary: the tokenizer.""" + from slime.utils.processing_utils import load_tokenizer + + return lambda: load_tokenizer(args.hf_checkpoint, trust_remote_code=True) + + +def group_index_of(group): + indexes = {getattr(sample, "group_index", None) for sample in group} + if len(indexes) != 1 or type(next(iter(indexes), None)) is not int: + raise ValueError("Slime must provide one group identity before native generation") + return indexes.pop() + + def assert_locked_policy_versions(ir_groups, policy_version): """Fail closed if any generation escaped the locked weight version.""" for group in ir_groups: @@ -32,11 +73,128 @@ def assert_locked_policy_versions(ir_groups, policy_version): raise ValueError("native rollout observed a stale policy version") +async def generate_group(args, group, sampling_params_, rollout_id, evaluation=False): + """Run one complete native group; evaluation is intentionally unsupported.""" + from harborrl.math_registry import NATIVE_TASK_PROVIDER + + if evaluation: + raise ValueError("native training rollout cannot be reused as evaluation") + if len(group) != args.n_samples_per_prompt: + raise ValueError("native rollout group is incomplete before launch") + entries = [task_entry(sample) for sample in group] + if any(entry != entries[0] for entry in entries[1:]): + raise ValueError("native rollout group mixes task locks") + entry = entries[0] + report = NATIVE_TASK_PROVIDER.adapter.inspect( + entry["path"], expected_digest=entry["task_digest"] + ) + if report["status"] != "NEEDS_PROBE": + raise ValueError(f"native task preflight failed: {report['reasons']}") + stop = list(sampling_params_.get("stop") or []) + sampling_digest = digest({ + "temperature": sampling_params_.get("temperature"), + "top_p": sampling_params_.get("top_p"), + "top_k": sampling_params_.get("top_k"), + "max_new_tokens": sampling_params_.get("max_new_tokens"), + "stop": stop, + }) + config = runtime_config_from_args(args) + runtime_ = await runtime(config, tokenizer_loader=slime_tokenizer_loader(args)) + policy_version = current_policy_version(runtime_) + trajectories = await run_group( + config, runtime_, entry=entry, group_index=group_index_of(group), + rollout_id=rollout_id, sampling_digest=sampling_digest, + policy_version=policy_version, + ) + return bind_group_samples(group, trajectories) + + +def bind_group_samples(group, trajectories): + """Attach trajectory evidence to Slime samples; order defines the slots.""" + output = [] + for sample, ir in zip(group, trajectories): + sample.metadata["native_ir"] = ir + sample.response = "native Harbor trial" + sample.reward = {"score": ir["evaluation"]["training_reward"]} + sample.status = sample.Status.COMPLETED + try: + sample.policy_version = int(ir["identity"]["policy_version"]) + except ValueError as exc: + raise ValueError("native policy version must be an integer in Slime") from exc + output.append(sample) + return output + + +async def run_service_groups(args, groups, rollout_id): + """Request complete groups from the manager-side RolloutService (P3).""" + from harborrl.backends.slime_v032.rollout_service_client import ( + RolloutServiceClient, + fetch_pool_states, + load_group_result, + service_group_request, + ) + + token_file = os.environ.get('HARBORRL_ROLLOUT_SERVICE_TOKEN_FILE') + token = Path(token_file).read_text().strip() if token_file else os.environ.get('HARBORRL_ROLLOUT_SERVICE_TOKEN', '') + client = RolloutServiceClient( + os.environ["HARBORRL_ROLLOUT_SERVICE_URL"], os.environ["RUN_ID"], + token) + router = f"http://{args.sglang_router_ip}:{args.sglang_router_port}" + # Policy lifecycle (§7): the GPU job publishes its serving endpoint, then + # the manager admits the version the complete pool actually serves. The + # manager never opens a version some engine has not reached yet. + await asyncio.to_thread(client.announce_serving, router) + states = await asyncio.to_thread(fetch_pool_states, router) + from harborrl.consistency.config import environment + from harborrl.consistency.sglang import admit_pool + if environment()['mode'] != 'off': + await asyncio.to_thread(admit_pool, states, os.environ["RUN_DIR"]) + await asyncio.to_thread(client.activate_policy, states, states[0]["weight_version"]) + # The manager's activation publishes the locked policy-pool record into + # the shared run directory; lock the same version before requesting. + policy_version = lock_policy_version(args) + params = sampling_params(args) + async def request_group(group): + request = service_group_request(group, params, rollout_id, policy_version) + payload = await asyncio.to_thread(client.submit_group, request) + # The HTTP layer returns a status summary; completed work carries the + # GroupResult itself. + result = payload.get("result") if isinstance(payload, dict) else None + if not isinstance(result, dict): + from harborrl.backends.slime_v032.rollout_service_client import ( + ServiceRolloutError, + ) + raise ServiceRolloutError( + f"group {request['group_id']} is {payload.get('status')!r}: " + f"{payload.get('error', '')}") + irs = load_group_result(client.run_id, request, result, allow_synthetic=False) + bind_group_samples(group, irs) + return irs + + pending = [asyncio.create_task(request_group(group)) for group in groups] + try: + results = await asyncio.gather(*pending, return_exceptions=True) + # Retain independent groups' evidence, but never export a partial batch. + for result in results: + if isinstance(result, BaseException): + raise result + return results + except BaseException: + # HTTP workers keep running until the manager acknowledges cancellation. + try: + await asyncio.to_thread(client.cancel_run) + finally: + await asyncio.gather(*pending, return_exceptions=True) + raise + + async def run_native_groups(args, groups, policy_version, rollout_id): """Run every group concurrently and return only complete IR evidence.""" - from harborrl.rollout.native_generate import close_runtime, generate_group - params = sampling_params(args) + if os.environ.get("HARBORRL_ROLLOUT_SERVICE_URL"): + ir_groups = await run_service_groups(args, groups, rollout_id) + assert_locked_policy_versions(ir_groups, policy_version) + return ir_groups try: results = await asyncio.gather(*( generate_group(args, group, params, rollout_id=rollout_id) for group in groups diff --git a/harborrl/backends/slime_v032/rollout_service_client.py b/harborrl/backends/slime_v032/rollout_service_client.py new file mode 100644 index 000000000..4fc73e492 --- /dev/null +++ b/harborrl/backends/slime_v032/rollout_service_client.py @@ -0,0 +1,210 @@ +"""SlimeAdapter HTTP client (P3): complete groups from the RolloutService. + +The GPU job never trusts a GroupResult: every manifest digest is recomputed, +identities are checked against the request, the group must be complete with +at least two RL_READY slots, and the policy version must be the locked one. +After a transport timeout the client queries status; it never resubmits. +""" +from __future__ import annotations + +import json +import time +from pathlib import Path +from urllib import error, parse, request + +from harborrl.rollout.runtime import task_entry +from harborrl.trajectories.native import digest, read_json, require_ready + +SERVICE_SCHEMA = "harborrl-rollout-service-v1" + + +def fetch_pool_states(endpoint, *, timeout=120, opener=None): + """Read the serving pool states directly from the SGLang router. + + Mirrors SGLangBackend.pool_states without a tokenizer: the GPU job calls + this before asking the manager to admit the version it currently serves. + """ + import json as json_module + from urllib import request as url_request + + opener = opener or url_request.build_opener(url_request.ProxyHandler({})) + endpoint = endpoint.rstrip("/") + + def get(url): + with opener.open(url_request.Request(url), timeout=timeout) as response: + return json_module.load(response) + + workers = get(endpoint + "/workers").get("workers") + if not isinstance(workers, list) or not workers: + raise ServiceRolloutError("serving router returned no workers") + states = [] + for worker in workers: + engine = worker.get("url") if isinstance(worker, dict) else None + if not isinstance(engine, str) or not engine.strip(): + raise ServiceRolloutError("serving router returned an invalid worker URL") + engine = engine.rstrip("/") + version = get(engine + "/get_weight_version").get("weight_version") + if not isinstance(version, str) or not version.strip(): + raise ServiceRolloutError("missing serving weight version") + states.append({"endpoint": engine, "healthy": True, + "weight_version": version}) + versions = {state["weight_version"] for state in states} + if len(versions) != 1: + raise ServiceRolloutError( + f"serving pool is mid-update; retry after all engines agree: {sorted(versions)}") + return states + + +class ServiceRolloutError(RuntimeError): + def __init__(self, message, *, status=None, code=None): + super().__init__(message) + self.status, self.code = status, code + + +def service_group_request(group, sampling_params, rollout_id, policy_version): + """Build one GroupRequest from a Slime sample group.""" + entries = [task_entry(sample) for sample in group] + if any(entry != entries[0] for entry in entries[1:]): + raise ValueError("native rollout group mixes task locks") + indexes = {getattr(sample, "group_index", None) for sample in group} + if len(indexes) != 1 or type(next(iter(indexes), None)) is not int: + raise ValueError("Slime must provide one group identity before native generation") + index = indexes.pop() + return { + "group_id": f"group-{index}", + "rollout_id": int(rollout_id), + "group_index": index, + "policy_version": str(policy_version), + "task": entries[0], + "sampling": {key: sampling_params.get(key) for key in + ("temperature", "top_p", "top_k", "max_new_tokens", "stop")}, + "slots": [{"slot_id": str(slot)} for slot in range(len(group))], + } + + +class RolloutServiceClient: + """Synchronous urllib client; safe to call from asyncio.to_thread.""" + + def __init__(self, base_url, run_id, token, *, timeout=120, poll_interval=1.0, + opener=None): + self.base_url = base_url.rstrip("/") + self.run_id = run_id + self.token = token + self.timeout = timeout + self.poll_interval = poll_interval + self.opener = opener or request.build_opener(request.ProxyHandler({})) + + def _request(self, method, path, body=None): + url = f"{self.base_url}{path}" + data = json.dumps(body).encode() if body is not None else None + headers = {"Content-Type": "application/json"} + if self.token: + headers["Authorization"] = f"Bearer {self.token}" + req = request.Request(url, data=data, headers=headers, method=method) + try: + with self.opener.open(req, timeout=self.timeout) as response: + return json.load(response) + except error.HTTPError as exc: + try: + payload = json.load(exc) + except (ValueError, OSError): + payload = {"error": {"type": "transport", "message": str(exc)}} + error_payload = payload.get("error", payload) + raise ServiceRolloutError( + f"{method} {path}: {error_payload.get('type')}: " + f"{error_payload.get('message')}", + status=exc.code, code=error_payload.get("type")) from exc + except (error.URLError, OSError, ValueError) as exc: + raise ServiceRolloutError(f"{method} {path}: {exc}", + code="transport") from exc + + def health(self): + return self._request("GET", "/healthz") + + def register(self, manifest): + return self._request("POST", f"/runs/{parse.quote(self.run_id)}/register", + {"manifest": manifest}) + + def submit_group(self, group_request, *, max_attempts=None): + """Submit idempotently; on timeout poll status, never regenerate.""" + path = f"/runs/{parse.quote(self.run_id)}/groups" + try: + status = self._request('POST', path, group_request) + except ServiceRolloutError as exc: + if exc.code != 'transport': + raise + status = self.group_status(group_request['group_id']) + polls = 0 + while status['status'] in ('queued', 'running'): + if max_attempts is not None and polls >= max_attempts: + raise ServiceRolloutError('group polling deadline reached', code='transport') + polls += 1 + time.sleep(self.poll_interval) + status = self.group_status(group_request["group_id"]) + return status + + def group_status(self, group_id): + return self._request( + "GET", f"/runs/{parse.quote(self.run_id)}/groups/{parse.quote(group_id)}") + + def cancel_group(self, group_id): + return self._request( + "POST", + f"/runs/{parse.quote(self.run_id)}/groups/{parse.quote(group_id)}/cancel") + + def activate_policy(self, states, expected): + return self._request( + "POST", f"/runs/{parse.quote(self.run_id)}/policy/activate", + {"states": states, "expected": str(expected)}) + + def announce_serving(self, router_url): + return self._request( + "POST", f"/runs/{parse.quote(self.run_id)}/serving/announce", + {"router_url": router_url}) + + def drain(self): + return self._request("POST", f"/runs/{parse.quote(self.run_id)}/drain") + + def cancel_run(self): + return self._request("POST", f"/runs/{parse.quote(self.run_id)}/cancel") + + +def load_group_result(run_id, group_request, result, *, allow_synthetic=False): + """Validate a GroupResult and load its trajectory manifests in slot order.""" + if not isinstance(result, dict): + raise ServiceRolloutError("group result must be a mapping") + if result.get("status") != "complete": + raise ServiceRolloutError( + f"group is {result.get('status')!r}, not complete: {result.get('error', '')}") + if result.get("run_id") != run_id or result.get("group_id") != group_request["group_id"]: + raise ServiceRolloutError("group result identity does not match the request") + if result.get("request_digest") != digest(group_request): + raise ServiceRolloutError("group result digest does not match the request") + slots = result.get("slots") + expected = [slot["slot_id"] for slot in group_request["slots"]] + if (not isinstance(slots, list) or len(slots) != len(expected) or len(slots) < 2 + or [slot.get("slot_id") for slot in slots] != expected): + raise ServiceRolloutError("group result is incomplete or reordered") + trajectories = [] + for descriptor, slot in zip(group_request["slots"], slots): + path = Path(slot["manifest_path"]) + try: + payload = read_json(path) + except (OSError, ValueError) as exc: + raise ServiceRolloutError(f"cannot read slot manifest: {exc}") from exc + if digest(payload) != slot.get("manifest_sha256"): + raise ServiceRolloutError(f"slot {descriptor['slot_id']} manifest digest mismatch") + identity = payload.get("identity", {}) + if (identity.get("policy_version") != group_request["policy_version"] + or identity.get("task_digest") != group_request["task"]["task_digest"] + or identity.get("run_id") != run_id + or identity.get("group_id") != group_request["group_id"] + or identity.get("slot_id") != descriptor["slot_id"]): + raise ServiceRolloutError( + f"slot {descriptor['slot_id']} identity differs from the request") + try: + require_ready(payload, allow_synthetic=allow_synthetic) + except ValueError as exc: + raise ServiceRolloutError(f"slot {descriptor['slot_id']}: {exc}") from exc + trajectories.append(payload) + return trajectories diff --git a/harborrl/cli.py b/harborrl/cli.py index 55530e0a1..0ea67a8a0 100644 --- a/harborrl/cli.py +++ b/harborrl/cli.py @@ -4,18 +4,35 @@ import argparse import json import sys - -ALGORITHM_CONTRACTS = {"grpo": "slime-v0.3.2-native-v1"} +from pathlib import Path def main(argv=None): argv = list(sys.argv[1:] if argv is None else argv) + if argv and argv[0] == "web": + from harborrl.webui.cli import main as web_main + + return web_main(argv[1:]) + if argv and argv[0] in {"options", "validate", "init"}: + from harborrl.options.cli import main as options_main + + return options_main(argv) + if argv and argv[0] == "list": + from harborrl.math_registry import list_components + + choices = {"models", "algorithms", "tasks", "harnesses"} + if len(argv) != 2 or argv[1] not in choices: + parser = argparse.ArgumentParser(prog="harborrl list") + parser.error("choose one of: " + ", ".join(sorted(choices))) + print(json.dumps(list_components()[argv[1]], indent=2)) + return 0 if argv and argv[0] == "hub": from harborrl.tasks.cli import main as hub_main return hub_main(argv[1:]) parser = argparse.ArgumentParser( - prog="harborrl", description=__doc__, allow_abbrev=False + prog="harborrl", description=__doc__, allow_abbrev=False, + epilog="Configuration tools: harborrl options --help; harborrl validate --help; harborrl init --help; harborrl web --help." ) parser.add_argument("-model", help="Slime model preset to select") parser.add_argument("-harness", help="agent harness to select") @@ -24,10 +41,13 @@ def main(argv=None): parser.add_argument("command", choices=["train", "doctor"]) parser.add_argument("config_path", nargs="?", help="YAML configuration file") parser.add_argument("--config", dest="config_option", help="YAML configuration file") + parser.add_argument("--deployment", help="deployment YAML describing the manager, GPU target and rollout workers") parser.add_argument("--set", action="append", default=[]) parser.add_argument("--dry-run", action="store_true") args = parser.parse_args(argv) from harborrl.config import launch_plan, load_config + from harborrl.deployment.schema import embedded_local, load_deployment + from harborrl.deployment.schema import summary as deployment_summary if args.config_path and args.config_option: parser.error( @@ -46,20 +66,36 @@ def main(argv=None): if args.harness is not None: overrides.append(f"harness.name={args.harness.replace('-', '_')}") if args.alg is not None: - contract = ALGORITHM_CONTRACTS.get(args.alg) - if contract is None: + from harborrl.math_registry import MATH_REGISTRY + + matches = [ + spec for spec in MATH_REGISTRY.algorithms.items() + if args.alg in (spec.id,) + ] + if not matches: parser.error( f"unsupported algorithm: {args.alg}; " - f"supported algorithms: {', '.join(sorted(ALGORITHM_CONTRACTS))}" + f"supported algorithms: " + f"{', '.join(spec.id for spec in MATH_REGISTRY.algorithms.items())}" ) + contract = next(iter(matches[0].training_backend_contracts)) overrides.append(f"training.backend_contract={contract}") try: - plan = launch_plan(load_config( + config = load_config( config_path, overrides, task_ids=None if args.task is None else [args.task], - )) + ) + deployment, deployment_source = load_deployment(args.deployment) + if deployment_source == "embedded-local": + deployment = embedded_local( + run_root=str(Path(config["output"]["root"]).resolve()), + workers=config["harbor"]["workers"], + expected_gpus=config["deployment"]["num_gpus"], + ) + plan = launch_plan(config) + plan["deployment"] = deployment_summary(deployment, deployment_source) except (TypeError, ValueError, OSError) as exc: parser.error(str(exc)) if args.dry_run: diff --git a/harborrl/config/native.py b/harborrl/config/native.py index 227f6e4d3..50b3ddb86 100644 --- a/harborrl/config/native.py +++ b/harborrl/config/native.py @@ -1,19 +1,18 @@ """Strict native schema 2; local paths resolve relative to the config file.""" -import json import os import re -import sys from pathlib import Path from urllib.parse import urlsplit import yaml -from harborrl.rollout.harbor_job.bindings import claude_config -from harborrl.trajectories.native import RewardProfile +from harborrl.consistency.config import DEFAULTS as CONSISTENCY_DEFAULTS +from harborrl.consistency.config import validate as validate_consistency FIELDS = { + 'consistency': set(CONSISTENCY_DEFAULTS), 'execution': {'backend'}, 'tasks': {'catalog'}, 'harness': {'name', 'profile'}, - 'harbor': {'python', 'version', 'workers'}, + 'harbor': {'python', 'version', 'workers', 'runner_transport', 'environment'}, 'gateway': {'host', 'port', 'advertised_url', 'tokenizer_digest', 'template_digest', 'audited_raw_logprobs'}, 'model': {'checkpoint', 'reference', 'args_file'}, 'training': {'num_rollout', 'learning_rate', 'save_interval', 'backend_contract'}, @@ -21,6 +20,7 @@ 'deployment': {'layout', 'num_gpus', 'actor_gpus', 'rollout_gpus', 'actor_tensor_parallel_size', 'rollout_gpus_per_engine'}, 'output': {'root'}, } +OPTIONAL = {'harbor': {'runner_transport', 'environment'}} class SelectedConfig(dict): @@ -32,7 +32,10 @@ def __init__(self, config, task_ids): def load_config(path, overrides=None, task_ids=None): """Load one schema-2 YAML file and apply strict ``section.key=value`` overrides.""" path = Path(path).resolve() - config = yaml.safe_load(path.read_text()) + try: + config = yaml.safe_load(path.read_text()) + except yaml.YAMLError as exc: + raise ValueError(f'configuration YAML: {exc}') from exc if not isinstance(config, dict): raise TypeError('configuration must be a mapping') if config.get('schema_version') != 2: @@ -45,8 +48,12 @@ def load_config(path, overrides=None, task_ids=None): if (not separator or field not in FIELDS.get(section_name, ()) or not isinstance(section, dict)): raise ValueError(f'unknown native override: {key}') - section[field] = yaml.safe_load(value) + try: + section[field] = yaml.safe_load(value) + except yaml.YAMLError as exc: + raise ValueError(f'{key}: invalid YAML override') from exc config = validate(config, path) + validate_files(config) if task_ids is None: return config if (not isinstance(task_ids, (list, tuple)) or not task_ids @@ -57,16 +64,19 @@ def load_config(path, overrides=None, task_ids=None): def validate(config, path): - from harborrl.trajectories.native import read_json + if not isinstance(config, dict): + raise TypeError('configuration must be a mapping') + config['consistency'] = validate_consistency(config.get('consistency')) if set(config) != set(FIELDS) | {'schema_version'} or type(config['schema_version']) is not int or config['schema_version'] != 2: raise ValueError('native schema requires exactly the documented schema 2 sections') + if not isinstance(config['training'], dict): + raise TypeError('training must be a mapping') config['training'].setdefault( 'backend_contract', 'slime-v0.3.2-native-v1') for section, fields in FIELDS.items(): - if not isinstance(config[section], dict) or set(config[section]) != fields: + actual = set(config[section]) if isinstance(config[section], dict) else set() + if not isinstance(config[section], dict) or not fields - OPTIONAL.get(section, set()) <= actual <= fields: raise ValueError(f'{section} requires exactly {sorted(fields)}') - if config['execution']['backend'] != 'harbor_job' or config['harness']['name'] != 'claude_code' or config['harbor']['version'] != '0.23.0': - raise ValueError('native requires harbor_job / claude_code / Harbor 0.23.0') for section, key in [('tasks','catalog'), ('harness','profile'), ('harbor','python'), ('model','checkpoint'), ('model','reference'), ('output','root')]: value = config[section][key] @@ -82,133 +92,90 @@ def validate(config, path): raise ValueError(f'{section}.{key} must be a positive integer') from harborrl.backends.slime_v032.versions import CONFIG_CONTRACTS from harborrl.trajectories.native import finite - if config['training']['backend_contract'] not in CONFIG_CONTRACTS: + if (not isinstance(config['training']['backend_contract'], str) + or config['training']['backend_contract'] not in CONFIG_CONTRACTS): raise ValueError(f"training.backend_contract must be one of {sorted(CONFIG_CONTRACTS)}") if not finite(config['training']['learning_rate']) or config['training']['learning_rate'] <= 0: - raise ValueError('positive finite learning_rate required') + raise ValueError('training.learning_rate must be positive and finite') g, d, s = config['gateway'], config['deployment'], config['sampling'] if type(g['audited_raw_logprobs']) is not bool or not 0 < g['port'] < 65536: - raise ValueError('invalid gateway audit flag or port') + raise ValueError('gateway.audited_raw_logprobs must be boolean and gateway.port must be in 1..65535') for key in ('host','tokenizer_digest','template_digest'): if not isinstance(g[key], str) or not g[key].strip(): raise ValueError(f'gateway.{key} required') + if not isinstance(g['advertised_url'], str): + raise TypeError('gateway.advertised_url must be a string') advertised = urlsplit(g['advertised_url']) if (advertised.scheme not in ('http', 'https') or not advertised.hostname or advertised.username or advertised.password or advertised.query or advertised.fragment or advertised.path not in ('', '/') or advertised.port != g['port']): raise ValueError('advertised gateway must be a plain origin and match listener port') - if s['group_size'] < 2 or s['max_tokens'] >= s['max_context']: - raise ValueError('invalid group or context budget') - if d['layout'] not in ('split','colocate'): + if s['group_size'] < 2: + raise ValueError('sampling.group_size must be at least 2') + if s['max_tokens'] >= s['max_context']: + raise ValueError('sampling.max_tokens must be below sampling.max_context') + if not isinstance(d['layout'], str) or d['layout'] not in ('split','colocate'): raise ValueError('layout must be split or colocate') if d['layout'] == 'split' and d['actor_gpus'] + d['rollout_gpus'] > d['num_gpus']: - raise ValueError('GPU budget exceeded') + raise ValueError('GPU budget exceeded: deployment.actor_gpus + deployment.rollout_gpus > deployment.num_gpus') if d['layout'] == 'colocate' and not d['actor_gpus'] == d['rollout_gpus'] == d['num_gpus']: raise ValueError('colocate GPU counts must match') if d['actor_gpus'] % d['actor_tensor_parallel_size'] or d['rollout_gpus'] % d['rollout_gpus_per_engine']: - raise ValueError('GPU counts must be divisible by TP') + raise ValueError('deployment GPU counts must be divisible by their tensor parallel sizes') preset = config['model']['args_file'] if not isinstance(preset,str) or not re.fullmatch(r'[A-Za-z0-9_-]+',preset): raise ValueError('model.args_file must name a Slime model preset; SLIME_DIR is checked by doctor') for key in ('tokenizer_digest', 'template_digest'): if not re.fullmatch(r'[0-9a-f]{64}', g[key]): raise ValueError(f'gateway.{key} must be a SHA-256 digest') - workers = config['harbor']['workers'] - if (not isinstance(workers, list) or not workers or any( + harbor = config['harbor'] + transport = harbor.get('runner_transport', 'ssh') + if transport not in ('ssh', 'local'): + raise ValueError('harbor.runner_transport must be ssh or local') + environment = harbor.get('environment', {'runtime': 'docker'}) + if not isinstance(environment, dict) or environment.get('runtime') not in ('docker', 'sandbox'): + raise ValueError('harbor.environment.runtime must be docker or sandbox') + expected = {'runtime', 'config'} if environment['runtime'] == 'sandbox' else {'runtime'} + if set(environment) != expected: + raise ValueError('invalid harbor.environment fields') + if environment['runtime'] == 'sandbox': + if not isinstance(environment['config'], str) or not environment['config']: + raise ValueError('sandbox config path required') + environment['config'] = str((Path(path).parent / environment['config']).resolve()) + workers = harbor['workers'] + if (not isinstance(workers, list) or (transport == 'ssh' and not workers) or any( not isinstance(worker, str) or not re.fullmatch(r'[A-Za-z0-9_.:+@-]+', worker) or worker.startswith('-') for worker in workers)): raise ValueError('harbor.workers must contain nonempty SSH destinations') - claude_config(read_json(config['harness']['profile']), g['advertised_url'], 'validation-only') + if transport == 'local' and workers: + raise ValueError('local runner requires empty harbor.workers') + from harborrl.math_registry import select_math, validate_combination + validate_combination(select_math(config), config) return config -def catalog(config): - from harborrl.data.harbor.native_inspector import inspect_native +def validate_files(config): + """Check harness profile files separately from pure schema validation.""" + from harborrl.math_registry import select_math from harborrl.trajectories.native import read_json - path = Path(config['tasks']['catalog']) - entries = read_json(path) - if not isinstance(entries,list) or not entries: - raise ValueError('catalog must be a nonempty JSON list') - seen = set() - for entry in entries: - if (set(entry) != {'id','revision','path','task_digest','reward_profile'} - or not isinstance(entry['id'], str) or not entry['id'].strip() - or not isinstance(entry['revision'], str) or not entry['revision'].strip() - or entry['id'] in seen): - raise ValueError('invalid or duplicate catalog identity') - if (not isinstance(entry['path'], str) or not entry['path'].strip() - or not isinstance(entry['task_digest'], str) - or not re.fullmatch(r'[0-9a-f]{64}', entry['task_digest']) - or not isinstance(entry['reward_profile'], dict)): - raise ValueError(f'invalid native task lock: {entry["id"]}') - seen.add(entry['id']) - entry['path'] = str((path.parent / entry['path']).resolve()) - report = inspect_native(entry['path'], expected_digest=entry['task_digest']) - if report['status'] != 'NEEDS_PROBE': - raise ValueError(f"task preflight failed: {entry['id']}: {report['reasons']}") - RewardProfile(**entry['reward_profile']) - selected = set(getattr(config, 'task_ids', ())) - if selected: - missing = selected - {entry['id'] for entry in entries} - if missing: - raise ValueError(f'task selector not found in catalog: {min(missing)}') - entries = [entry for entry in entries if entry['id'] in selected] - return entries + harness = select_math(config).harness + try: + harness.adapter.validate_profile( + read_json(config['harness']['profile']), + config['gateway']['advertised_url'], + ) + except (TypeError, ValueError, KeyError) as exc: + raise ValueError(f'harness.profile: {exc}') from exc + return config + + +def catalog(config): + """Compatibility entry point for task lock validation.""" + from harborrl.tasks.catalog import catalog as inspect_catalog + return inspect_catalog(config) def launch_plan(config): - from harborrl.backends.slime_v032.versions import runtime_contract_id - from harborrl.config import ROOT - catalog(config) - c = config - worker_overrides = os.environ.get('HARBORRL_NATIVE_TASK_WORKER_OVERRIDES', '{}') - try: - overrides = json.loads(worker_overrides) - valid_workers = set(c['harbor']['workers']) - if (not isinstance(overrides, dict) or any(not isinstance(key, str) or value not in valid_workers - for key, value in overrides.items())): - raise ValueError - except (ValueError, json.JSONDecodeError) as exc: - raise ValueError('invalid HARBORRL_NATIVE_TASK_WORKER_OVERRIDES') from exc - environment = { - 'DATASET': 'native', 'ALGO': 'grpo', 'HARNESS_OPTION': 'native-claude-code', - 'HF_CKPT': c['model']['checkpoint'], 'REF_LOAD': c['model']['reference'], - 'MODEL_ARGS_FILE': c['model']['args_file'], 'MODEL_TAG': c['model']['args_file'], - 'NUM_ROLLOUT': c['training']['num_rollout'], 'SAVE_INTERVAL': c['training']['save_interval'], - 'TRAIN_PYTHON': sys.executable, - 'ROLLOUT_BATCH_SIZE': c['sampling']['groups_per_batch'], - 'N_SAMPLES': c['sampling']['group_size'], - 'ROLLOUT_MAX_RESPONSE_LEN': c['sampling']['max_tokens'], - 'ROLLOUT_MAX_CONTEXT_LEN': c['sampling']['max_context'], - 'NUM_GPUS': c['deployment']['num_gpus'], 'ACTOR_GPUS': c['deployment']['actor_gpus'], - 'ROLLOUT_GPUS': c['deployment']['rollout_gpus'], - 'TP_SIZE': c['deployment']['actor_tensor_parallel_size'], - 'ROLLOUT_NUM_GPUS_PER_ENGINE': c['deployment']['rollout_gpus_per_engine'], - 'HARBORRL_GPU_LAYOUT': c['deployment']['layout'], - 'HARBORRL_STRUCTURED_LAUNCH': '1', 'HARBORRL_VERIFY_POLICY_POOL': '1', - 'HARBORRL_SKIP_GLOBAL_CLEANUP': '1', 'SLIME_RAY_PLACEMENT_GPU_PROBE': '1', - 'PYTORCH_CUDA_ALLOC_CONF': ('max_split_size_mb:2048' if c['deployment']['layout'] == 'colocate' - else 'max_split_size_mb:2048,expandable_segments:True'), - 'HARBORRL_NATIVE_ROLLOUT': '1', - 'HARBORRL_NATIVE_SLIME_CONTRACT': runtime_contract_id(c['training']['backend_contract']), - 'HARBORRL_NATIVE_RUNNER_PYTHON': c['harbor']['python'], - 'HARBORRL_NATIVE_RUNNER_WORKERS': json.dumps(c['harbor']['workers']), - 'HARBORRL_NATIVE_TASK_WORKER_OVERRIDES': json.dumps(overrides, sort_keys=True), - 'HARBORRL_NATIVE_PROFILE': c['harness']['profile'], - 'HARBORRL_NATIVE_CATALOG': c['tasks']['catalog'], - 'HARBORRL_NATIVE_GATEWAY_HOST': c['gateway']['host'], - 'HARBORRL_NATIVE_GATEWAY_PORT': c['gateway']['port'], - 'HARBORRL_NATIVE_GATEWAY_URL': c['gateway']['advertised_url'], - 'HARBORRL_NATIVE_TOKENIZER_DIGEST': c['gateway']['tokenizer_digest'], - 'HARBORRL_NATIVE_TEMPLATE_DIGEST': c['gateway']['template_digest'], - 'HARBORRL_NATIVE_AUDITED_LOGPROBS': str(int(c['gateway']['audited_raw_logprobs'])), - 'HARBORRL_NATIVE_MAX_ATTEMPTS': c['sampling']['max_attempts'], - 'HARBORRL_NATIVE_DRAIN_TIMEOUT': '30', - 'RUNS_ROOT': c['output']['root'], 'WANDB_ENABLE': '0', - } - return {'config': config, 'command': [sys.executable, '-m', 'harborrl.platform.native_train'], - 'environment': {key: str(value) for key, value in environment.items()}, - 'training_command': ['bash', str(ROOT / 'harborrl/platform/slime_train.sh')], - 'required_services': ['GPU Ray / SGLang / Messages Gateway', 'external CPU Harbor Docker workers'], - 'launcher': 'native Claude Code → Messages Gateway → Harbor verifier → Slime GRPO', - 'layout': {'mode': config['deployment']['layout'], 'offload': config['deployment']['layout']=='colocate'}} + """Compatibility entry point for backend launch mapping.""" + from harborrl.backends.slime_v032.config import launch_plan as build_plan + return build_plan(config) diff --git a/harborrl/consistency/__init__.py b/harborrl/consistency/__init__.py new file mode 100644 index 000000000..8031995e5 --- /dev/null +++ b/harborrl/consistency/__init__.py @@ -0,0 +1 @@ +"""Backend consistency evidence, independent of optimization objectives.""" diff --git a/harborrl/consistency/actor.py b/harborrl/consistency/actor.py new file mode 100644 index 000000000..e332fc4d1 --- /dev/null +++ b/harborrl/consistency/actor.py @@ -0,0 +1,88 @@ +"""Small Slime actor extension: snapshot trained-model canaries before weight sync.""" +import os +from pathlib import Path + +import torch +import torch.distributed as dist +from slime.backends.megatron_utils.actor import MegatronTrainRayActor +from slime.backends.megatron_utils.data import DataIterator +from slime.backends.megatron_utils.loss import get_log_probs_and_entropy +from slime.backends.megatron_utils.model import forward_only +from slime.backends.megatron_utils.update_weight.common import named_params_and_buffers +from slime.utils.distributed_utils import get_gloo_group +from slime.utils.reloadable_process_group import ( + destroy_process_groups, + reload_process_groups, +) + +from harborrl.trajectories.native import digest, publish + + +class ConsistencyActor(MegatronTrainRayActor): + def update_weights(self): + if self.args.offload_train: + self.wake_up() + self._switch_model("actor") + from harborrl.rollout.runtime import tokenizer_fingerprint + error = None + try: + actual = tokenizer_fingerprint(self.tokenizer) + expected = (os.environ['HARBORRL_NATIVE_TOKENIZER_DIGEST'], os.environ['HARBORRL_NATIVE_TEMPLATE_DIGEST']) + if actual != expected: + raise ValueError('Trainer tokenizer/template differs from rollout launch identity') + except (ValueError, TypeError, KeyError) as exc: + error = str(exc) + errors = [None] * dist.get_world_size() + dist.all_gather_object(errors, error, group=get_gloo_group()) + if any(errors): + raise ValueError(f'Invalid trainer identity: {errors}') + cases = [] + # Literal token fixtures intentionally bypass all serving-side text templating. + for prompt, answer in (("Compute 1 + 1 =", " 2.\n"), ("def square(x):\n", " return x * x\n")): + p = self.tokenizer.encode(prompt, add_special_tokens=True) + r = self.tokenizer.encode(answer, add_special_tokens=False) + if not p or not r: + raise ValueError("Tokenizer produced an empty canary") + cases.append({"prompt_ids": p, "response_ids": r}) + device = next(self.model[0].parameters()).device + data = {"tokens": [torch.tensor(c["prompt_ids"] + c["response_ids"], device=device) for c in cases], + "loss_masks": [torch.ones(len(c["response_ids"]), dtype=torch.int, device=device) for c in cases], + "total_lengths": [len(c["prompt_ids"]) + len(c["response_ids"]) for c in cases], + "response_lengths": [len(c["response_ids"]) for c in cases]} + output = forward_only(get_log_probs_and_entropy, self.args, self.model, + [DataIterator(data, [[i] for i in range(len(cases))])], [len(cases)]) + for case, values in zip(cases, output["log_probs"], strict=True): + case["logprobs"] = values.detach().float().cpu().tolist() + fingerprints = [] + for name, parameter in named_params_and_buffers(self.args, self.model): + flat = parameter.detach().reshape(-1) + indices = torch.linspace(0, max(0, flat.numel() - 1), min(16, flat.numel()), device=flat.device).long() + fingerprints.append({"name": name, "shape": list(parameter.shape), "dtype": str(parameter.dtype), + "sample": flat[indices].float().cpu().tolist()}) + local = {"rank": dist.get_rank(), "fingerprint": digest(fingerprints), "tensor_count": len(fingerprints)} + shards = [None] * dist.get_world_size() + dist.all_gather_object(shards, local, group=get_gloo_group()) + config = self.hf_config.to_dict() + reference = {"schema_version": "harborrl-sync-reference-v1", "cases": cases, + "model_config_digest": digest(config), "model_config": config, + "tokenizer_vocabulary_digest": digest(sorted(self.tokenizer.get_vocab().items())), + "parameter_shards": shards, "fingerprint_semantics": "16 deterministic values per tensor; not a full checksum"} + if self.args.offload_train: + self.sleep() + super().update_weights() + if self.args.offload_train: + reload_process_groups() + reference["weight_version"] = str(self.weight_updater.weight_version) + reference["digest"] = digest(reference) + error = None + if dist.get_rank() == 0: + try: + publish(Path(os.environ["RUN_DIR"]) / "consistency" / f"sync-{reference['weight_version']}.json", reference) + except (OSError, ValueError) as exc: + error = str(exc) + errors = [None] * dist.get_world_size() + dist.all_gather_object(errors, error, group=get_gloo_group()) + if self.args.offload_train: + destroy_process_groups() + if any(errors): + raise ValueError(f"Failed to publish synchronized model evidence: {errors}") diff --git a/harborrl/consistency/config.py b/harborrl/consistency/config.py new file mode 100644 index 000000000..f2317e49a --- /dev/null +++ b/harborrl/consistency/config.py @@ -0,0 +1,51 @@ +"""Explicit deployment-specific tolerances; no universal numeric threshold.""" +import json +import os + +from harborrl.trajectories.native import finite + +DEFAULTS = {"mode": "off", "max_abs_delta": None, "max_mean_abs_delta": None, + "bad_token_delta": None, "max_bad_fraction": 0.0, "worst_tokens": 8} + + +def validate(value=None): + if value is None: + value = {} + if not isinstance(value, dict) or set(value) - set(DEFAULTS): + raise ValueError("Unknown consistency configuration fields") + result = {**DEFAULTS, **value} + if result["mode"] not in ("off", "audit", "enforce"): + raise ValueError("consistency.mode must be off, audit or enforce") + for key in ("max_abs_delta", "max_mean_abs_delta", "bad_token_delta"): + if result[key] is not None and (not finite(result[key]) or result[key] < 0): + raise ValueError(f"consistency.{key} must be nonnegative and finite") + if result["mode"] == "enforce" and result["max_abs_delta"] is None: + raise ValueError("enforce requires an explicit max_abs_delta for this deployment") + if not finite(result["max_bad_fraction"]) or not 0 <= result["max_bad_fraction"] <= 1: + raise ValueError("max_bad_fraction must be in [0, 1]") + if type(result["worst_tokens"]) is not int or not 0 <= result["worst_tokens"] <= 32: + raise ValueError("worst_tokens must be an integer in [0, 32]") + return result + + +def environment(): + return validate(json.loads(os.environ.get("HARBORRL_CONSISTENCY", "{}"))) + + +def require_raw_sampling(sampling): + allowed = {"temperature", "top_p", "top_k", "max_new_tokens", "stop", "raw_logprobs"} + if (not isinstance(sampling, dict) or set(sampling) - allowed + or sampling.get("temperature") != 1 or sampling.get("top_p") != 1 + or sampling.get("top_k", -1) not in (-1, 0) + or sampling.get("raw_logprobs", True) is not True): + raise ValueError("Consistency v1 requires untransformed raw-policy sampling") + + +def violations(metrics, config): + errors = [] + for option, field in (("max_abs_delta", "max_abs_delta"), ("max_mean_abs_delta", "mean_abs_delta")): + if config[option] is not None and metrics[field] > config[option]: + errors.append(f"{field}={metrics[field]:.8g} exceeds {config[option]}") + if config["bad_token_delta"] is not None and metrics["bad_fraction"] > config["max_bad_fraction"]: + errors.append(f"bad_fraction={metrics['bad_fraction']:.8g} exceeds {config['max_bad_fraction']}") + return errors diff --git a/harborrl/consistency/metrics.py b/harborrl/consistency/metrics.py new file mode 100644 index 000000000..4057e8c97 --- /dev/null +++ b/harborrl/consistency/metrics.py @@ -0,0 +1,67 @@ +"""Mergeable token statistics with bounded-memory percentile upper bounds.""" +import bisect +import math + +from harborrl.trajectories.native import finite + +# Logarithmic buckets retain sub-micro precision near zero, with no token all-gather. +EDGES = [0.0, *[2 ** (-24 + i / 8) for i in range(321)]] + + +def statistics(rows, *, threshold=None, worst=8): + result = {"count": 0, "signed_sum": 0.0, "abs_sum": 0.0, "max_abs_delta": 0.0, + "min_log_ratio": None, "max_log_ratio": None, "bad_count": 0, + "histogram": [0] * (len(EDGES) + 1), "worst": []} + for row_index, (current, behavior, mask) in enumerate(rows): + if len(current) != len(behavior) or len(mask) != len(current) or not len(current): + raise ValueError("Probability/mask length mismatch") + if any(flag not in (0, 1) for flag in mask): + raise ValueError("Loss mask must be binary") + for position, (p, q, flag) in enumerate(zip(current, behavior, mask, strict=True)): + if not finite(p) or not finite(q) or p > 0 or q > 0: + raise ValueError("Log probabilities must be finite and nonpositive") + if not flag: + continue + delta = float(p) - float(q) + absolute = abs(delta) + if not math.isfinite(delta): + raise ValueError("Nonfinite log-probability difference") + result["count"] += 1 + result["signed_sum"] += delta + result["abs_sum"] += absolute + result["max_abs_delta"] = max(result["max_abs_delta"], absolute) + result["min_log_ratio"] = delta if result["min_log_ratio"] is None else min(result["min_log_ratio"], delta) + result["max_log_ratio"] = delta if result["max_log_ratio"] is None else max(result["max_log_ratio"], delta) + result["bad_count"] += int(threshold is not None and absolute > threshold) + result["histogram"][bisect.bisect_left(EDGES, absolute)] += 1 + if worst: + result["worst"].append({"row": row_index, "position": position, "delta": delta}) + result["worst"] = sorted(result["worst"], key=lambda x: abs(x["delta"]), reverse=True)[:worst] + if not result["count"]: + raise ValueError("No valid response tokens") + if not math.isfinite(result["abs_sum"]) or not math.isfinite(result["signed_sum"]): + raise ValueError("Probability statistics overflow") + return result + + +def merge(parts): + if not parts: + raise ValueError("No contributing ranks") + count = sum(p["count"] for p in parts) + bins = [sum(p["histogram"][i] for p in parts) for i in range(len(EDGES) + 1)] + maximum = max(p["max_abs_delta"] for p in parts) + def percentile(fraction): + target, seen = math.ceil(count * fraction), 0 + for index, n in enumerate(bins): + seen += n + if seen >= target: + return min(EDGES[index], maximum) if index < len(EDGES) else maximum + raise ValueError("Corrupt histogram") + return {"token_count": count, "signed_mean_delta": math.fsum(p["signed_sum"] for p in parts) / count, + "mean_abs_delta": math.fsum(p["abs_sum"] for p in parts) / count, + "max_abs_delta": maximum, "p95_abs_delta_upper": percentile(.95), "p99_abs_delta_upper": percentile(.99), + "min_log_ratio": min(p["min_log_ratio"] for p in parts), + "max_log_ratio": max(p["max_log_ratio"] for p in parts), + "bad_fraction": sum(p["bad_count"] for p in parts) / count, + "percentile_semantics": "logarithmic-histogram upper bound; not exact quantiles", + "gap_semantics": "fixed-prefix token log-ratio; not full-sequence KL"} diff --git a/harborrl/consistency/probe.py b/harborrl/consistency/probe.py new file mode 100644 index 000000000..a218337b1 --- /dev/null +++ b/harborrl/consistency/probe.py @@ -0,0 +1,35 @@ +"""Replay a Megatron fixed-token reference against an explicit engine pool.""" +import argparse +import json + +from harborrl.trajectories.native import digest, publish, read_json + +from .config import validate +from .sglang import check_pool, http_json + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--reference', required=True) + parser.add_argument('--engine', action='append', required=True) + parser.add_argument('--config', required=True, help='JSON consistency mapping with explicit tolerances') + parser.add_argument('--output', required=True) + args = parser.parse_args(argv) + settings = validate(read_json(args.config)) + if settings['mode'] == 'off': + parser.error('probe mode must be audit or enforce') + reference = read_json(args.reference) + if reference.get('digest') != digest({k: v for k, v in reference.items() if k != 'digest'}): + raise ValueError('Corrupt sync reference') + states = [{'endpoint': endpoint.rstrip('/'), 'healthy': True, + 'weight_version': http_json(endpoint.rstrip('/') + '/get_weight_version')['weight_version']} + for endpoint in args.engine] + report = check_pool(states, reference, settings) + report['reference_digest'] = reference['digest'] + publish(args.output, report) + print(json.dumps(report, indent=2)) + return int(report['status'] == 'failed') + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/harborrl/consistency/report.py b/harborrl/consistency/report.py new file mode 100644 index 000000000..0b3962894 --- /dev/null +++ b/harborrl/consistency/report.py @@ -0,0 +1,51 @@ +"""Collect errors before raising so every participating rank makes the same decision.""" +from pathlib import Path + +from harborrl.trajectories.native import publish + +from .config import violations +from .metrics import merge, statistics + + +def assess(rows, config, *, metadata=None): + try: + return {"statistics": statistics(rows, threshold=config["bad_token_delta"], worst=config["worst_tokens"]), + "error": None, "metadata": metadata or {}} + except (ValueError, TypeError, OverflowError) as exc: + return {"statistics": None, "error": f"{type(exc).__name__}: {exc}", "metadata": metadata or {}} + + +def decide(parts, config, *, phase): + structural = [p["error"] for p in parts if p["error"]] + contributing = [p for p in parts if p.get("contribute", True)] + metrics = None if structural else merge([p["statistics"] for p in contributing]) + numeric = violations(metrics, config) if metrics else [] + return {"schema_version": "harborrl-consistency-v1", "phase": phase, "mode": config["mode"], + "status": "failed" if structural or (numeric and config["mode"] == "enforce") else "warning" if numeric else "passed", + "structural_errors": structural, "numeric_violations": numeric, "metrics": metrics, + "config": config, "ranks": [{"metadata": p["metadata"], "error": p["error"], + "contribute": p.get("contribute", True), + "worst": p["statistics"]["worst"] if p["statistics"] else []} for p in parts]} + + +def distributed_gate(part, config, path, *, phase, group=None): + import torch.distributed as dist + active = dist.is_available() and dist.is_initialized() + parts = [part] + if active: + parts = [None] * dist.get_world_size(group) + dist.all_gather_object(parts, part, group=group) + report = decide(parts, config, phase=phase) + write_error = None + if not active or dist.get_rank(group) == 0: + try: + publish(Path(path), report) + except (OSError, ValueError) as exc: + write_error = str(exc) + if active: + errors = [None] * dist.get_world_size(group) + dist.all_gather_object(errors, write_error, group=group) + write_error = next((error for error in errors if error), None) + if write_error or report["status"] == "failed": + raise ValueError(f"Consistency {phase} rejected: {write_error or report['structural_errors'] or report['numeric_violations']}") + return report diff --git a/harborrl/consistency/sglang.py b/harborrl/consistency/sglang.py new file mode 100644 index 000000000..d9a5ac654 --- /dev/null +++ b/harborrl/consistency/sglang.py @@ -0,0 +1,92 @@ +"""Exact-token teacher-forcing probes against every synchronized inference engine.""" +import json +from pathlib import Path +from urllib import request + +from harborrl.trajectories.native import digest, finite, publish, read_json + +from .config import environment, violations +from .metrics import merge +from .report import assess, decide + + +def http_json(url, payload=None, timeout=120): + opener = request.build_opener(request.ProxyHandler({})) + req = request.Request(url, data=json.dumps(payload).encode() if payload is not None else None, + headers={"Content-Type": "application/json"}) + with opener.open(req, timeout=timeout) as response: + return json.load(response) + + +def score(endpoint, prompt, response, version, *, transport=http_json): + tokens = prompt + response + payload = {"input_ids": tokens, "sampling_params": {"temperature": 1.0, "top_p": 1.0, + "top_k": -1, "max_new_tokens": 0}, "return_logprob": True, "logprob_start_len": 0} + output = transport(endpoint.rstrip("/") + "/generate", payload) + meta = output.get("meta_info", {}) + if str(meta.get("weight_version", "")) != str(version): + raise ValueError("Canary generation-time weight version mismatch") + rows = meta.get("input_token_logprobs") + if (not isinstance(rows, list) or len(rows) != len(tokens) + or any(not isinstance(row, (tuple, list)) or len(row) < 2 for row in rows) + or [row[1] for row in rows] != tokens): + raise ValueError("Canary input logprobs must preserve every exact token and position") + values = [row[0] for row in rows[len(prompt):]] + if any(not finite(value) or value > 0 for value in values): + raise ValueError("Invalid canary raw logprob") + return values + + +def check_pool(states, reference, config, *, transport=http_json): + expected = reference["weight_version"] + endpoints = [s["endpoint"] for s in states] + if not states or len(set(endpoints)) != len(endpoints): + raise ValueError("Canary requires a nonempty unique engine pool") + if any(not s.get("healthy") or str(s.get("weight_version")) != str(expected) for s in states): + raise ValueError("Canary pool contains stale or unhealthy engines") + parts = [] + for endpoint in endpoints: + try: + rows = [(case["logprobs"], score(endpoint, case["prompt_ids"], case["response_ids"], expected, + transport=transport), [1] * len(case["response_ids"])) for case in reference["cases"]] + part = assess(rows, config, metadata={"engine": endpoint, "weight_version": expected}) + # Detect a version transition across scoring, in addition to each response's version. + if str(transport(endpoint + "/get_weight_version").get("weight_version")) != str(expected): + raise ValueError("Engine changed weights during canary scoring") + except (OSError, ValueError, TypeError, KeyError) as exc: + part = {"statistics": None, "error": str(exc), "metadata": {"engine": endpoint}} + parts.append(part) + report = decide(parts, config, phase="synchronized-pool") + # Mean/fraction limits apply independently to every engine. + engine_errors = [f"{p['metadata']['engine']}: {error}" for p in parts if p['statistics'] + for error in violations(merge([p['statistics']]), config)] + report['numeric_violations'] = engine_errors + if engine_errors and report['status'] != 'failed': + report['status'] = 'failed' if config['mode'] == 'enforce' else 'warning' + return report + + +def admit_pool(states, run_dir): + config = environment() + if config["mode"] == "off": + return + versions = {str(s["weight_version"]) for s in states} + if len(versions) != 1: + raise ValueError("Cannot admit a mixed-version pool") + version = versions.pop() + if not version.isdecimal(): + raise ValueError("Invalid numeric serving version") + root = Path(run_dir) / "consistency" + reference = read_json(root / f"sync-{version}.json") + if reference["weight_version"] != version or reference["digest"] != digest({k: v for k, v in reference.items() if k != "digest"}): + raise ValueError("Canary reference identity mismatch") + admission = digest({'states': states, 'config': config, 'reference': reference['digest']}) + path = root / f"pool-{version}-{admission}.json" + if path.exists(): + report = read_json(path) + else: + report = check_pool(states, reference, config) + report["reference_digest"] = reference["digest"] + publish(path, report) + if report["status"] == "failed": + raise ValueError(f"Serving canary rejected version {version}") diff --git a/harborrl/consistency/slime.py b/harborrl/consistency/slime.py new file mode 100644 index 000000000..6493e5ea1 --- /dev/null +++ b/harborrl/consistency/slime.py @@ -0,0 +1,74 @@ +"""Integration using the locked Slime precomputed log_probs and postprocess hook.""" +import os +import struct +from pathlib import Path + +from harborrl.trajectories.native import digest, publish, read_json + +from .config import environment, require_raw_sampling +from .report import assess, distributed_gate + + +def evidence_key(tokens, logprobs, mask): + q = [struct.unpack("!f", struct.pack("!f", value))[0] for value in logprobs] + return digest({"tokens": tokens, "q": q, "mask": mask}) + + +def record_turn(turn, tokens, logprobs, mask): + config = environment() + if config["mode"] == "off": + return + require_raw_sampling(turn["sampling"]) + if turn["logprob_semantics"] != "raw_model" or turn["evidence_kind"] != "serving": + raise ValueError("Consistency requires observed raw serving evidence") + key = evidence_key(tokens, logprobs, mask) + version = str(turn["policy_version"]) + if not version.isdecimal(): + raise ValueError("Invalid serving version") + payload = {"key": key, "weight_version": version, "sampling": turn["sampling"], + "tokenizer_digest": turn["tokenizer_digest"], "template_digest": turn["template_digest"], + "logprob_semantics": "raw_model_equals_sampling", "evidence_kind": "serving"} + publish(Path(os.environ["RUN_DIR"]) / "consistency" / "turns" / version / f"{key}.json", payload) + + +def identity_tis(*args, **kwargs): + raise RuntimeError("Consistency is metrics-only; invoking a TIS correction would change the Native objective") + + +def check_training_batch(args, rollout_id, data, validator): + import torch.distributed as dist + from megatron.core import mpu + from slime.utils.distributed_utils import get_gloo_group + + from harborrl.backends.slime_v032.versions import read_locked_policy_version + + config = environment() + root = Path(os.environ["RUN_DIR"]) + metadata = {"rank": dist.get_rank(), "rollout_id": rollout_id, "backend": "megatron", + "scope": "pre-update actual packed rollout batch"} + try: + validator(args, rollout_id, data) + if "log_probs" not in data or len(data["log_probs"]) != len(data["tokens"]): + raise ValueError("Missing pre-update Megatron log_probs; --get-mismatch-metrics is required") + version = read_locked_policy_version(root) + metadata["weight_version"] = version + cases = [] + for tokens, q, mask, p0 in zip(data["tokens"], data["rollout_log_probs"], data["loss_masks"], data["log_probs"], strict=True): + tokens, q, mask, p0 = (x.detach().cpu().tolist() if hasattr(x, "detach") else list(x) for x in (tokens, q, mask, p0)) + # Slime stores rollout probabilities as float32. Normalize the lookup identically. + key = evidence_key(tokens, q, mask) + evidence = read_json(root / "consistency" / "turns" / version / f"{key}.json") + if evidence["key"] != key or evidence["weight_version"] != version: + raise ValueError("Frozen serving evidence does not match the training batch") + for field, env in (("tokenizer_digest", "HARBORRL_NATIVE_TOKENIZER_DIGEST"), + ("template_digest", "HARBORRL_NATIVE_TEMPLATE_DIGEST")): + if evidence[field] != os.environ[env]: + raise ValueError(f"Training batch {field} differs from launch identity") + require_raw_sampling(evidence["sampling"]) + cases.append((p0, q, mask)) + part = assess(cases, config, metadata=metadata) + except (ValueError, TypeError, KeyError, OSError) as exc: + part = {"statistics": None, "error": str(exc), "metadata": metadata} + part["contribute"] = mpu.get_tensor_model_parallel_rank() == 0 + distributed_gate(part, config, root / "consistency" / f"batch-{rollout_id}.json", + phase="pre-update-backend-gap", group=get_gloo_group()) diff --git a/harborrl/consistency/train.py b/harborrl/consistency/train.py new file mode 100644 index 000000000..caaea02d9 --- /dev/null +++ b/harborrl/consistency/train.py @@ -0,0 +1,76 @@ +"""Run the locked upstream training loop with its supported actor-class factory.""" +import functools +import importlib.util +import json +import os +from pathlib import Path + +from .config import environment + + +class CheckedActorGroup: + """Check every sync, including the final one, before returning to upstream.""" + def __init__(self, actor, manager, args): + self.actor, self.manager, self.args = actor, manager, args + + def __getattr__(self, name): + return getattr(self.actor, name) + + def update_weights(self): + import ray + + from harborrl.backends.slime_v032.rollout_service_client import ( + fetch_pool_states, + ) + + from .sglang import admit_pool + result = self.actor.update_weights() + if self.args.offload_rollout: + ray.get(self.manager.onload_kv.remote()) + endpoint = f"http://{self.args.sglang_router_ip}:{self.args.sglang_router_port}" + admit_pool(fetch_pool_states(endpoint), os.environ['RUN_DIR']) + return result + + +def main(): + import ray + from slime.ray.placement_group import create_training_models + from slime.utils.arguments import parse_args + + from .actor import ConsistencyActor + + args = parse_args() + for name, expected in (("pipeline_model_parallel_size", 1), ("context_parallel_size", 1), + ("update_weights_interval", 1)): + if getattr(args, name) != expected: + raise ValueError(f"Consistency v1 requires {name}={expected}") + if any(getattr(args, key, False) for key in ('use_critic', 'keep_old_actor', 'use_tis', 'release_train', 'use_rollout_routing_replay', 'num_experts')): + raise ValueError("Consistency v1 supports synchronous Native actor training without routing replay") + if args.update_weight_mode != "full" or args.update_weight_transport != "nccl": + raise ValueError("Consistency v1 requires full tensor/NCCL weight synchronization") + settings = environment() + rollout_env = {key: value for key, value in os.environ.items() + if key.startswith('HARBORRL_') or key in ('RUN_DIR', 'RUN_ID', 'MODEL_TAG', 'MODEL_ARGS_FILE', + 'ROLLOUT_MAX_RESPONSE_LEN', 'ROLLOUT_MAX_CONTEXT_LEN', 'PYTHONPATH')} + if not ray.is_initialized(): + ray.init(runtime_env={'env_vars': rollout_env}) + args.get_mismatch_metrics = True + args.custom_tis_function_path = "harborrl.consistency.slime.identity_tis" + args.train_env_vars = dict(args.train_env_vars or {}) + args.train_env_vars.update({"HARBORRL_CONSISTENCY": json.dumps(settings), "RUN_DIR": os.environ["RUN_DIR"], + "HARBORRL_NATIVE_TOKENIZER_DIGEST": os.environ["HARBORRL_NATIVE_TOKENIZER_DIGEST"], + "HARBORRL_NATIVE_TEMPLATE_DIGEST": os.environ["HARBORRL_NATIVE_TEMPLATE_DIGEST"]}) + spec = importlib.util.spec_from_file_location("harborrl_upstream_train", Path(os.environ["SLIME_DIR"]) / "train.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + # Only the factory in this private entrypoint module is replaced; upstream files are untouched. + factory = functools.partial(create_training_models, actor_cls=ConsistencyActor) + def checked_factory(args, pgs, manager): + actor, critic = factory(args, pgs, manager) + return CheckedActorGroup(actor, manager, args), critic + module.create_training_models = checked_factory + module.train(args) + + +if __name__ == "__main__": + main() diff --git a/harborrl/data/harbor/__main__.py b/harborrl/data/harbor/__main__.py index 99d90d197..291463cbd 100644 --- a/harborrl/data/harbor/__main__.py +++ b/harborrl/data/harbor/__main__.py @@ -1,3 +1,4 @@ +"""Legacy TerminalEnv compatibility CLI; use harborrl hub inspect for native tasks.""" import argparse import json from pathlib import Path diff --git a/harborrl/data/harbor/inspector.py b/harborrl/data/harbor/inspector.py index 4a426fd4f..3499bdbd2 100644 --- a/harborrl/data/harbor/inspector.py +++ b/harborrl/data/harbor/inspector.py @@ -1,35 +1,16 @@ -"""Conservative inspection of pinned local Harbor tasks.""" +"""Legacy TerminalEnv compatibility tooling; native training does not use this path.""" -import hashlib from dataclasses import asdict, dataclass from pathlib import Path -from .receipt import parse_reward +from harborrl.tasks.content import tomllib, tree_digest # compatibility exports -try: - import tomllib -except ModuleNotFoundError: # Python 3.10 - import tomli as tomllib +from .receipt import parse_reward PROFILE = "terminal_text_v1" REWARD_PROFILE = "harbor_reward_txt_v1" -def tree_digest(root: Path, *, exclude: tuple[str, ...] = ()) -> str: - h = hashlib.sha256() - for path in sorted(Path(root).rglob("*")): - if str(path.relative_to(root)) in exclude: - continue - if path.is_symlink(): - raise ValueError("symlinks are not supported") - if path.is_file(): - data = path.read_bytes() - h.update(str(path.relative_to(root)).encode() + b"\0") - h.update(str(path.stat().st_mode & 0o777).encode() + b"\0") - h.update(str(len(data)).encode() + b"\0" + data) - return h.hexdigest() - - @dataclass(frozen=True) class TaskRef: dataset: str diff --git a/harborrl/data/harbor/materializer.py b/harborrl/data/harbor/materializer.py index 95346c414..ce0c59cec 100644 --- a/harborrl/data/harbor/materializer.py +++ b/harborrl/data/harbor/materializer.py @@ -1,4 +1,4 @@ -"""Publish a task without changing its verifier or Docker build context.""" +"""Legacy TerminalEnv compatibility tooling; native training does not use this path.""" import json import shutil diff --git a/harborrl/data/harbor/native_inspector.py b/harborrl/data/harbor/native_inspector.py index de0a311b5..729f3c726 100644 --- a/harborrl/data/harbor/native_inspector.py +++ b/harborrl/data/harbor/native_inspector.py @@ -1,7 +1,7 @@ """Native preflight: task metadata, not TerminalEnv materialization rules.""" from pathlib import Path -from .inspector import tomllib, tree_digest +from harborrl.tasks.content import tomllib, tree_digest def inspect_native(path, *, expected_digest=None): @@ -14,7 +14,7 @@ def inspect_native(path, *, expected_digest=None): if expected_digest is not None and checksum != expected_digest: raise ValueError("task content changed since catalog lock") spec = tomllib.loads((root / "task.toml").read_text()) - for name in ("instruction.md", "environment/Dockerfile", "tests/test.sh"): + for name in ("instruction.md", "tests/test.sh"): if not (root / name).is_file(): raise ValueError("missing " + name) if not (root / "instruction.md").read_text().strip(): @@ -22,6 +22,8 @@ def inspect_native(path, *, expected_digest=None): env, verifier = spec.get("environment", {}), spec.get("verifier", {}) if not isinstance(env, dict) or not isinstance(verifier, dict): raise TypeError("invalid environment/verifier configuration") + if not (root / 'environment/Dockerfile').is_file() and not env.get('docker_image'): + raise ValueError('missing environment/Dockerfile or prebuilt docker_image') if not isinstance(spec.get("agent", {}), dict): raise TypeError("invalid agent configuration") if spec.get("user_agent") or spec.get("bridge"): diff --git a/harborrl/data/harbor/probe.py b/harborrl/data/harbor/probe.py index 650941bb9..d3e11d948 100644 --- a/harborrl/data/harbor/probe.py +++ b/harborrl/data/harbor/probe.py @@ -1,11 +1,4 @@ -"""Probe a Harbor task with native Docker verifier semantics. - -The legacy TerminalEnv comparison path is intentionally not part of the public -release. This probe runs the untouched task verifier twice in isolated -containers and requires the same scalar reward, which keeps the check aligned -with the Native Harbor Runner boundary without importing the old environment -stack. -""" +"""Legacy TerminalEnv compatibility tooling; native training does not use this path.""" from __future__ import annotations import asyncio diff --git a/harborrl/data/harbor/receipt.py b/harborrl/data/harbor/receipt.py index 425a8be4f..ad0476ab1 100644 --- a/harborrl/data/harbor/receipt.py +++ b/harborrl/data/harbor/receipt.py @@ -1,4 +1,4 @@ -"""Strict scalar receipt parsing: a missing/invalid score is an infra failure.""" +"""Legacy TerminalEnv compatibility tooling; native training does not use this path.""" import math diff --git a/harborrl/deployment/__init__.py b/harborrl/deployment/__init__.py new file mode 100644 index 000000000..cfae85518 --- /dev/null +++ b/harborrl/deployment/__init__.py @@ -0,0 +1 @@ +"""Deployment topology: machine roles, preflight and remote training targets.""" diff --git a/harborrl/deployment/controller.py b/harborrl/deployment/controller.py new file mode 100644 index 000000000..0ee3700e3 --- /dev/null +++ b/harborrl/deployment/controller.py @@ -0,0 +1,207 @@ +"""Service-mode dispatch: the manager-side controller flow (P1 + P3). + +An independent CPU manager runs the same preflight graph, materializes the +run on shared storage, starts the RolloutService control plane, launches the +existing Slime job on the GPU target over SSH, and waits for the shared +exit-code record. Rollout execution itself moves to the manager only through +the service; training stays on the GPU. +""" +from __future__ import annotations + +import contextlib +import json +import os +import signal +import sys +import threading +from pathlib import Path + +from harborrl.config import ROOT +from harborrl.deployment import remote +from harborrl.deployment.schema import destination +from harborrl.deployment.training_target import SshTrainingTarget +from harborrl.platform.evidence import ( + _materialize, + _source_audit, + effective_config, + new_run_root, + run_environment, +) +from harborrl.rollout.service import RolloutService, make_control_server +from harborrl.trajectories.native import publish + +TOKEN_MODE = 0o600 + + +def check_gpu_control(deployment): + manager, gpu = deployment['manager'], deployment['gpu_target'] + timeout = deployment['connectivity']['http_timeout_s'] + result = remote.run_ssh(gpu['ssh'] or {'host': 'localhost'}, + f"curl --noproxy '*' -fsS --max-time {timeout} " + remote.quote(manager['control_url'] + '/healthz'), + timeout=timeout + 15) + if result.returncode != 0: + raise RuntimeError('GPU cannot reach manager control service') + payload = json.loads(result.stdout) + if payload != {'ok': True, 'service': 'harborrl-rollout-service-v1'}: + raise RuntimeError('GPU reached an unexpected control service') + return {'gpu_to_controller': True, 'control_url': manager['control_url']} + + +def _ensure_token(token_file): + """Load the run token, creating a fresh one on first use.""" + path = Path(token_file) + if path.is_file(): + token = path.read_text().strip() + if token: + return token + import secrets + + token = secrets.token_urlsafe(32) + path.parent.mkdir(parents=True, exist_ok=True) + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, TOKEN_MODE) + with os.fdopen(fd, "w") as stream: + stream.write(token + "\n") + return token + + +def _rollout_environment(plan, deployment, environment): + """Environment overlay that points the GPU job at the manager service.""" + manager = deployment["manager"] + workers = deployment["rollout"]["workers"] + overlay = dict(environment) + overlay.update({ + "HARBORRL_ROLLOUT_MODE": "service", + "HARBORRL_ROLLOUT_SERVICE_URL": manager["control_url"], + "HARBORRL_ROLLOUT_SERVICE_TOKEN_FILE": manager['run_token_file'], + "TRAIN_PYTHON": deployment['gpu_target']['train_python'], + "HARBORRL_NATIVE_GATEWAY_HOST": manager["bind"], + "HARBORRL_NATIVE_GATEWAY_PORT": str(manager["gateway_port"]), + "HARBORRL_NATIVE_GATEWAY_URL": manager["gateway_url"], + "HARBORRL_NATIVE_RUNNER_WORKERS": json.dumps( + [destination(worker["ssh"]) for worker in workers]), + "HARBORRL_NATIVE_RUNNER_PYTHON": (plan['config']['harbor']['python'] + if deployment['rollout']['provider'] == 'sandbox' + else workers[0]['runner_python']), + }) + if deployment['rollout']['provider'] == 'sandbox': + if 'HARBORRL_NATIVE_SANDBOX' not in overlay: + raise ValueError('sandbox deployment requires sandbox training config') + transport = plan['config']['harbor'].get('runner_transport', 'ssh') + if (transport == 'local' and workers) or (transport == 'ssh' and not workers): + raise ValueError('sandbox runner transport and deployment workers disagree') + overlay['HARBORRL_NATIVE_RUNNER_TRANSPORT'] = transport + return overlay + + +def _runtime_factory(deployment, environment, *, concurrency_limit, group_size, tokenizer=None): + """Build the per-run rollout runtime when the GPU job announces serving.""" + + def factory(run_id, router_url): + from harborrl.rollout.runtime import RolloutRuntime, RolloutRuntimeConfig + + env = {**os.environ, **environment} + config = RolloutRuntimeConfig.from_environment( + env, endpoint=router_url, concurrency_limit=concurrency_limit, + group_size=group_size, + gateway_host=deployment["manager"]["bind"], + gateway_port=deployment["manager"]["gateway_port"], + gateway_url=deployment["manager"]["gateway_url"]) + resolved = tokenizer + if resolved is None: + from transformers import AutoTokenizer + + resolved = AutoTokenizer.from_pretrained( + env["HF_CKPT"], trust_remote_code=True) + return RolloutRuntime(config, resolved) + + return factory + + +def dispatch_service(plan, deployment, *, checks): + manager, gpu, storage = deployment["manager"], deployment["gpu_target"], deployment["storage"] + config = plan["config"] + root = new_run_root(storage["run_root"]) + root.mkdir(parents=True, mode=0o700) + run_id = root.name + + catalog_path, prompt_path = _materialize(plan, root) + source = _source_audit(root) + environment = run_environment(plan, root, catalog_path, prompt_path) + token = _ensure_token(manager["run_token_file"]) + environment = _rollout_environment(plan, deployment, environment) + + sampling = config["sampling"] + concurrency = sampling["group_size"] * sampling["groups_per_batch"] + service = RolloutService( + run_root=root, + runtime_factory=_runtime_factory( + deployment, environment, + concurrency_limit=concurrency, group_size=sampling["group_size"]), + token_file=manager["run_token_file"]) + control = make_control_server(service, host=manager["bind"], + port=manager["control_port"]) + control_thread = None + target = None + exit_code = 1 + try: + service.register_run(run_id, { + "run_id": run_id, "deployment_source": plan["deployment"]["source"], + "source_commit": source["commit"], + "config_digest": plan["deployment"]["digest"], + "group_size": sampling["group_size"], + "workers": [worker["id"] for worker in deployment["rollout"]["workers"]], + }, token=token) + control_thread = threading.Thread( + target=control.serve_forever, name="harborrl-control-plane", daemon=True) + control_thread.start() + publish(root / 'control-readiness.json', check_gpu_control(deployment)) + + publish(root / "launch.json", { + "plan": plan, "run_dir": str(root), "source": source, + "materialized_catalog": str(catalog_path), "prompt_data": str(prompt_path), + "control_url": manager["control_url"]}) + publish(root / "effective-config.json", + effective_config(plan, environment, dict(environment), source, checks)) + + target = SshTrainingTarget( + gpu["ssh"] or {"host": "localhost"}, source_dir=gpu["source_dir"], run_dir=root, + environment_file=gpu.get("environment_file"), + command_timeout=deployment["connectivity"]["command_timeout_s"] + 30, + ssh_timeout=deployment["connectivity"]["ssh_timeout_s"]) + # The launcher executes inside the GPU checkout; manager-side absolute + # paths are rewritten so shared-storage artifacts keep one spelling. + target.write_launcher( + ["bash", str(ROOT / "harborrl/platform/slime_train.sh")], environment, + path_rewrites=[(str(manager["source_dir"]), str(gpu["source_dir"]))]) + target.launch() + + def forward_signal(signum, frame): + if target is not None: + target.cancel() + + previous = {sig: signal.signal(sig, forward_signal) + for sig in (signal.SIGINT, signal.SIGTERM)} + try: + exit_code = target.wait() + finally: + for sig, handler in previous.items(): + signal.signal(sig, handler) + except BaseException: + import traceback + + traceback.print_exc() + if target is not None: + # Best-effort teardown; the run record keeps the failure evidence. + with contextlib.suppress(Exception): + target.cancel() + raise + finally: + with contextlib.suppress(Exception): + service.drain_run(run_id) + service.close() + if control_thread is not None: + control.shutdown() + control_thread.join(5) + control.server_close() + print(json.dumps({"run_dir": str(root), "exit_code": exit_code}), file=sys.stderr) + return exit_code diff --git a/harborrl/deployment/preflight.py b/harborrl/deployment/preflight.py new file mode 100644 index 000000000..94cb6fd6f --- /dev/null +++ b/harborrl/deployment/preflight.py @@ -0,0 +1,340 @@ +"""Role-layered preflight graph shared by train, doctor and quickstart. + +Every check names the role and machine it ran against, the outcome, and a +user-executable fix. The graph never raises: failures are data, and any +failed mandatory check keeps a run from being created. +""" +from __future__ import annotations + +import json +import socket +import subprocess +import time +from pathlib import Path +from urllib.parse import urlsplit + +from harborrl.deployment import remote +from harborrl.deployment.schema import destination + + +def _check(role, machine, name, ok, detail="", hint=None, duration_ms=None): + record = {"role": role, "machine": machine, "check": name, "ok": bool(ok), + "detail": str(detail)} + if hint: + record["hint"] = str(hint) + if duration_ms is not None: + record["duration_ms"] = round(duration_ms, 3) + return record + + +def _timed(record_factory): + started = time.monotonic() + record = record_factory() + record["duration_ms"] = (time.monotonic() - started) * 1000 + return record + + +def _bindable(host, port): + """Probe with the same address reuse semantics as our HTTP servers.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + try: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind((host, port)) + server.listen() + return True, "port is free" + except OSError as exc: + return False, f"cannot bind manager listener on {host}:{port}: {exc}" + + +def _manager_checks(deployment): + manager, storage = deployment["manager"], deployment["storage"] + checks = [] + run_root = Path(storage["run_root"]) + exists = run_root.is_dir() + writable = exists and _touch_ok(run_root) if storage["manager_writable"] else exists + checks.append(_timed(lambda: _check( + "manager", manager["id"], "storage.run_root", exists and writable, str(run_root), + hint="create the shared run root and grant the manager write access"))) + + if manager["mode"] == "service": + token = Path(manager["run_token_file"]) + token_ok = token.is_file() + detail = str(token) + if token_ok: + mode = token.stat().st_mode & 0o777 + token_ok = mode & 0o077 == 0 + detail = f"{token} mode {oct(mode)}" + checks.append(_timed(lambda: _check( + "manager", manager["id"], "manager.run_token_file", token_ok, detail, + hint="install the run-scoped token with chmod 600"))) + + for name, port in (("manager.control_port", manager["control_port"]), + ("manager.gateway_port", manager["gateway_port"])): + started = time.monotonic() + ok, detail = _bindable(manager["bind"], port) + checks.append(_check( + "manager", manager["id"], name, ok, detail, + hint=f"free the port or stop the conflicting listener on {manager['bind']}:{port}", + duration_ms=(time.monotonic() - started) * 1000)) + return checks + + +def _touch_ok(directory): + probe = directory / ".harborrl-preflight-probe" + try: + probe.write_text("write probe\n") + probe.unlink() + return True + except OSError: + return False + + +def _local_runner(): + def run(command, *, timeout=30): + try: + return subprocess.run(command, capture_output=True, text=True, + timeout=timeout, check=False, shell=isinstance(command, str)) + except (subprocess.TimeoutExpired, OSError) as exc: + return subprocess.CompletedProcess(command, 255, "", f"{type(exc).__name__}: {exc}") + + def test(expression): + result = run(f"test {expression} && echo ok") + return result.returncode == 0 and result.stdout.strip() == "ok", result + + return run, test + + +def _gpu_checks(plan, deployment): + gpu, connectivity = deployment["gpu_target"], deployment["connectivity"] + config = plan["config"] + machine = gpu["id"] + checks = [] + if gpu["transport"] == "ssh": + spec = gpu["ssh"] + started = time.monotonic() + probe = remote.run_ssh(spec, "true", timeout=connectivity["ssh_timeout_s"] + 5, + connect_timeout=connectivity["ssh_timeout_s"]) + checks.append(_check( + "gpu_target", machine, "gpu_target.ssh", probe.returncode == 0, + probe.stderr.strip()[-300:] or "non-interactive ssh ok", + hint="the SSH config alias, agent or identity file must allow non-interactive login", + duration_ms=(time.monotonic() - started) * 1000)) + if probe.returncode != 0: + checks.append(_check( + "gpu_target", machine, "gpu_target.further_probes", True, + "skipped: fix gpu_target.ssh first, then rerun for path, model and GPU checks")) + return checks + + def execute(command): + return remote.run_ssh(spec, command, timeout=connectivity["command_timeout_s"], + connect_timeout=connectivity["ssh_timeout_s"]) + + def shell_test(expression): + ok, result = remote.test_remote(spec, expression, + timeout=connectivity["command_timeout_s"], + connect_timeout=connectivity["ssh_timeout_s"]) + return ok, result + else: + run, test = _local_runner() + + def execute(command): + return run(command, timeout=connectivity["command_timeout_s"]) + + def shell_test(expression): + return test(expression) + + def path_check(name, expression, target, hint): + started = time.monotonic() + ok, _result = shell_test(expression) + checks.append(_check("gpu_target", machine, name, ok, target, + hint=hint, duration_ms=(time.monotonic() - started) * 1000)) + + path_check("gpu_target.source_dir", f"-d {remote.quote(gpu['source_dir'])}", gpu["source_dir"], + "mount or clone the HarborRL checkout at gpu_target.source_dir") + path_check("gpu_target.train_python", f"-x {remote.quote(gpu['train_python'])}", gpu["train_python"], + "provision the training interpreter at gpu_target.train_python") + if gpu.get("environment_file"): + path_check("gpu_target.environment_file", + f"-f {remote.quote(gpu['environment_file'])}", gpu["environment_file"], + "provide the controlled environment variable map") + for key in ("checkpoint", "reference"): + target = config["model"][key] + path_check(f"model.{key}", f"-d {remote.quote(target)}", target, + "the model checkpoint must exist in GPU-machine path semantics") + + started = time.monotonic() + gpu_list = execute("nvidia-smi --query-gpu=index --format=csv,noheader") + visible = len([line for line in gpu_list.stdout.splitlines() if line.strip()]) + checks.append(_check( + "gpu_target", machine, "gpu_target.expected_gpus", + gpu_list.returncode == 0 and visible >= gpu["expected_gpus"], + f"{visible} visible GPUs (returncode {gpu_list.returncode})", + hint="check CUDA visibility and the GPU allocation before training", + duration_ms=(time.monotonic() - started) * 1000)) + return checks + + +def _worker_checks(plan, deployment): + workers, connectivity = deployment["rollout"]["workers"], deployment["connectivity"] + expected_harbor = plan["config"]["harbor"]["version"] + checks = [] + for worker in workers: + spec, machine = worker["ssh"], worker["id"] + started = time.monotonic() + probe = remote.run_ssh(spec, "true", timeout=connectivity["ssh_timeout_s"] + 5, + connect_timeout=connectivity["ssh_timeout_s"]) + checks.append(_check( + "rollout_worker", machine, "worker.ssh", probe.returncode == 0, + probe.stderr.strip()[-300:] or "non-interactive ssh ok", + hint=f"SSH access to {destination(spec)} must work without a password prompt", + duration_ms=(time.monotonic() - started) * 1000)) + if probe.returncode != 0: + continue + + def worker_test(spec, machine, name, expression, target, hint): + started = time.monotonic() + ok, _result = remote.test_remote(spec, expression, + timeout=connectivity["command_timeout_s"], + connect_timeout=connectivity["ssh_timeout_s"]) + checks.append(_check("rollout_worker", machine, name, ok, target, + hint=hint, duration_ms=(time.monotonic() - started) * 1000)) + + worker_test(spec, machine, "worker.source_dir", + f"-d {remote.quote(worker['source_dir'])}", + worker["source_dir"], "mount the HarborRL checkout on the worker") + worker_test(spec, machine, "worker.runner_python", + f"-f {remote.quote(worker['runner_python'])}", + worker["runner_python"], "provision the runner interpreter on the worker") + for index, task_root in enumerate(worker["task_roots"]): + worker_test(spec, machine, f"worker.task_roots[{index}]", + f"-d {remote.quote(task_root)}", + task_root, "mount the task tree on the worker") + + started = time.monotonic() + probe_command = (f"cd {remote.quote(worker['source_dir'])} && " + f"{remote.quote(worker['runner_python'])} -m " + "harborrl.rollout.harbor_job.runner --probe") + result = remote.run_ssh(spec, probe_command, timeout=connectivity["command_timeout_s"], + connect_timeout=connectivity["ssh_timeout_s"]) + runtime_ok = harbor_ok = False + detail = result.stderr.strip()[-300:] or result.stdout.strip()[-300:] + try: + payload = json.loads(result.stdout) + parts = payload.get("python", "").split(".")[:2] + runtime_ok = (result.returncode == 0 + and len(parts) == 2 and all(p.isdigit() for p in parts) + and tuple(map(int, parts)) >= (3, 12)) + harbor_ok = payload.get("harbor_version") == expected_harbor + if result.returncode == 0: + detail = f"python {payload.get('python')}, harbor {payload.get('harbor_version')}" + except (ValueError, AttributeError, TypeError): + pass + checks.append(_check( + "rollout_worker", machine, "worker.runner_runtime", runtime_ok, detail, + hint="runner Python must be >= 3.12 with the pinned Harbor installation", + duration_ms=(time.monotonic() - started) * 1000)) + checks.append(_check( + "rollout_worker", machine, "worker.harbor_version", harbor_ok, detail, + hint=f"pin Harbor {expected_harbor} in the runner environment", + duration_ms=(time.monotonic() - started) * 1000)) + + started = time.monotonic() + docker = remote.run_ssh(spec, "docker info --format '{{.ServerVersion}}'", + timeout=connectivity["command_timeout_s"], + connect_timeout=connectivity["ssh_timeout_s"]) + checks.append(_check( + "rollout_worker", machine, "worker.docker", docker.returncode == 0, + docker.stdout.strip()[:120] or docker.stderr.strip()[-200:], + hint="start the Docker daemon; Harbor trials require container execution", + duration_ms=(time.monotonic() - started) * 1000)) + return checks + + +def _control_loop_checks(deployment): + manager, connectivity = deployment["manager"], deployment["connectivity"] + checks = [] + if manager["mode"] != "service": + return checks + dials = (("connectivity.gpu_to_controller", manager["control_url"], connectivity["gpu_to_controller"]), + ("connectivity.worker_to_gateway", manager["gateway_url"], connectivity["worker_to_gateway"])) + for name, url, enabled in dials: + if not enabled: + continue + parts = urlsplit(url) + started = time.monotonic() + try: + socket.getaddrinfo(parts.hostname, parts.port) + ok, detail = True, "address resolves from the manager" + except socket.gaierror as exc: + ok, detail = False, f"{exc}" + checks.append(_check("control_loop", manager["id"], name, ok, detail, + hint="fix DNS or firewall so the address resolves for every role", + duration_ms=(time.monotonic() - started) * 1000)) + started = time.monotonic() + serving = False + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client: + client.settimeout(min(connectivity["http_timeout_s"], 5)) + serving = client.connect_ex((parts.hostname, parts.port)) == 0 + except OSError: + serving = False # unreachable now; the post-start dial retries + checks.append(_check( + "control_loop", manager["id"], name + ".dial", True, + "listener already serving" if serving + else "no listener yet; the live dial reruns after components start", + duration_ms=(time.monotonic() - started) * 1000)) + if connectivity["manager_to_gpu_inference"]: + checks.append(_check( + "control_loop", manager["id"], "connectivity.manager_to_gpu_inference", True, + "deferred: the GPU job publishes its inference endpoint after serving is healthy")) + return checks + + +def preflight(plan, deployment, *, live=True): + """Run every deployment layer; remote probes are skipped when live=False.""" + if not live: + return [_check("deployment", deployment["manager"]["id"], "deployment.config", + True, "dry-run: parsed and validated, remote probes skipped")] + checks = [] + checks.extend(_manager_checks(deployment)) + checks.extend(_gpu_checks(plan, deployment)) + if deployment["rollout"]["provider"] == "ssh_docker": + checks.extend(_worker_checks(plan, deployment)) + checks.extend(_control_loop_checks(deployment)) + return checks + + +def readiness_checks(plan, deployment, *, from_specs=()): + """Post-start dials: cross-machine health after components are listening. + + ``from_specs`` maps a role to the ssh spec that should perform the dial; + callers pass the GPU target and rollout workers of a live deployment. + """ + manager, connectivity = deployment["manager"], deployment["connectivity"] + checks = [] + dials = (("gpu_target", "gpu_to_controller.ready", manager["control_url"], + connectivity["gpu_to_controller"]), + ("rollout_worker", "worker_to_gateway.ready", manager["gateway_url"], + connectivity["worker_to_gateway"])) + specs = dict(from_specs) + for role, name, url, enabled in dials: + if not enabled: + continue + spec = specs.get(role) + if spec is None: + checks.append(_check("control_loop", manager["id"], name, True, + "deferred: no machine mapped for this dial")) + continue + path = "/healthz" if "control" in name else "/readyz" + command = (f"python3 -c " + f"\"import urllib.request;urllib.request.urlopen('{url}{path}'," + f"timeout={connectivity['http_timeout_s']})\"") + started = time.monotonic() + result = remote.run_ssh(spec, command, timeout=connectivity["command_timeout_s"], + connect_timeout=connectivity["ssh_timeout_s"]) + checks.append(_check("control_loop", destination(spec), name, + result.returncode == 0, + result.stderr.strip()[-300:] or "health dial ok", + hint=f"open the route from {destination(spec)} to {url}{path}", + duration_ms=(time.monotonic() - started) * 1000)) + return checks diff --git a/harborrl/deployment/remote.py b/harborrl/deployment/remote.py new file mode 100644 index 000000000..e9ddd276c --- /dev/null +++ b/harborrl/deployment/remote.py @@ -0,0 +1,55 @@ +"""Non-interactive SSH transport for deployment preflight and launch. + +Every invocation runs BatchMode: a password prompt is a failed check, never an +interactive hang. User, port and identity are inherited from the invoking +user's SSH config unless the deployment spec narrows them. +""" +from __future__ import annotations + +import shlex +import subprocess + +SSH_OPTIONS = ("BatchMode=yes", "ClearAllForwardings=yes", "StrictHostKeyChecking=accept-new") + + +def ssh_command(spec, remote_command, *, connect_timeout=15): + """Assemble one non-interactive ssh argv for a normalized spec.""" + argv = ["ssh"] + for option in SSH_OPTIONS: + argv += ["-o", option] + argv += ["-o", f"ConnectTimeout={int(connect_timeout)}"] + if spec.get("port"): + argv += ["-p", str(spec["port"])] + if spec.get("identity"): + argv += ["-i", str(spec["identity"])] + from harborrl.deployment.schema import destination + + argv += [destination(spec), remote_command] + return argv + + +def run_ssh(spec, command, *, timeout=30, connect_timeout=15): + """Run one remote command; transport failures surface as returncode 255.""" + argv = ssh_command(spec, command, connect_timeout=connect_timeout) + try: + return subprocess.run(argv, capture_output=True, text=True, + timeout=timeout, check=False) + except (subprocess.TimeoutExpired, OSError) as exc: + return subprocess.CompletedProcess(argv, 255, "", f"{type(exc).__name__}: {exc}") + + +def test_remote(spec, expression, *, timeout=30, connect_timeout=15): + """Evaluate a shell test expression on the target; True when it passes.""" + result = run_ssh(spec, f"test {expression} && echo ok", timeout=timeout, + connect_timeout=connect_timeout) + return result.returncode == 0 and result.stdout.strip() == "ok", result + + +def remote_read(spec, command, *, timeout=30, connect_timeout=15): + """Return (returncode, stdout, stderr) for a remote command.""" + result = run_ssh(spec, command, timeout=timeout, connect_timeout=connect_timeout) + return result.returncode, result.stdout, result.stderr + + +def quote(value): + return shlex.quote(str(value)) diff --git a/harborrl/deployment/schema.py b/harborrl/deployment/schema.py new file mode 100644 index 000000000..3cc7bf7c8 --- /dev/null +++ b/harborrl/deployment/schema.py @@ -0,0 +1,330 @@ +"""Deployment schema v1: machine roles and access, never training semantics. + +A deployment config describes WHERE the manager, GPU trainer and rollout +workers live and HOW they reach each other. It deliberately excludes every +training-side field (learning rate, sampling, task catalog) and never stores +secrets: SSH targets must resolve through the invoking user's SSH config, +agent or a restricted identity file. +""" +from __future__ import annotations + +import re +import sys +from pathlib import Path +from urllib.parse import urlsplit + +import yaml + +from harborrl.config import ROOT + +DEPLOYMENT_SCHEMA_VERSION = 1 +DEPLOYMENT_SECTIONS = {"manager", "gpu_target", "storage", "rollout", "connectivity"} + +SLUG = re.compile(r"[A-Za-z0-9][A-Za-z0-9_-]*") +LOOPBACK_HOSTS = {"localhost", "127.0.0.1", "::1", "0.0.0.0", "[::1]"} + +MANAGER_FIELDS = {"id", "mode", "bind", "control_port", "control_url", "gateway_port", + "gateway_url", "run_token_file", "source_dir", "controller_python"} +SSH_FIELDS = {"host", "user", "port", "identity"} +GPU_FIELDS = {"id", "transport", "ssh", "source_dir", "train_python", "environment_file", "expected_gpus"} +STORAGE_FIELDS = {"mode", "run_root", "manager_writable", "gpu_writable", "worker_writable"} +WORKER_FIELDS = {"id", "ssh", "source_dir", "runner_python", "docker", "task_roots"} +ROLLOUT_FIELDS = {"provider", "workers"} +CONNECTIVITY_FIELDS = {"command_timeout_s", "ssh_timeout_s", "http_timeout_s", + "gpu_to_controller", "worker_to_gateway", "manager_to_gpu_inference"} +TRANSPORTS = ("local", "ssh") +ROLLOUT_PROVIDERS = ("ssh_docker", "embedded", "sandbox") +STORAGE_MODES = ("shared_fs",) + + +class DeploymentError(ValueError): + """Raised for any malformed or unsafe deployment configuration.""" + + +def _slug(value, label): + if not isinstance(value, str) or not SLUG.fullmatch(value): + raise DeploymentError(f"{label} must be a short machine identifier") + return value + + +def _positive_int(value, label): + if type(value) is not int or value <= 0 or type(value) is bool: + raise DeploymentError(f"{label} must be a positive integer") + return value + + +def _nonempty_str(value, label): + if not isinstance(value, str) or not value.strip(): + raise DeploymentError(f"{label} must be a nonempty string") + return value + + +def _bool(value, label): + if type(value) is not bool: + raise DeploymentError(f"{label} must be a boolean") + return value + + +def _absolute_path(value, label): + value = _nonempty_str(value, label) + if not Path(value).is_absolute(): + raise DeploymentError(f"{label} must be an absolute path in target-machine semantics") + return value + + +def _plain_origin(url, label, *, port=None, allow_loopback=False): + parts = urlsplit(_nonempty_str(url, label)) + if (parts.scheme not in ("http", "https") or not parts.hostname + or parts.username or parts.password or parts.query or parts.fragment + or parts.path not in ("", "/") + or (port is not None and parts.port != port)): + raise DeploymentError(f"{label} must be a plain http(s) origin matching its declared port") + if not allow_loopback and parts.hostname.lower() in LOOPBACK_HOSTS: + raise DeploymentError(f"{label} must be reachable from other machines; loopback is forbidden") + return f"{parts.scheme}://{parts.netloc}".rstrip("/") + + +def _ssh_spec(value, label, *, require_host=True): + if value is None: + value = {} + if not isinstance(value, dict) or set(value) - SSH_FIELDS: + raise DeploymentError( + f"{label} supports only {sorted(SSH_FIELDS)}; passwords and private keys are never stored") + host = value.get("host") + if host is not None: + host = _nonempty_str(host, f"{label}.host") + if host.startswith("-") or host.count("@") > 1: + raise DeploymentError(f"{label}.host must be an SSH destination, not a flag") + if require_host and not host: + raise DeploymentError(f"{label}.host is required") + user = value.get("user") + if user is not None: + user = _nonempty_str(user, f"{label}.user") + if user.startswith("-"): + raise DeploymentError(f"{label}.user must not look like a flag") + port = value.get("port") + if port is not None: + port = _positive_int(port, f"{label}.port") + identity = value.get("identity") + if identity is not None: + identity = _absolute_path(identity, f"{label}.identity") + spec = {"host": host} + if user: + spec["user"] = user + if port: + spec["port"] = port + if identity: + spec["identity"] = identity + return spec + + +def destination(spec): + """Render an ssh destination from a normalized spec (user@host).""" + return f"{spec['user']}@{spec['host']}" if spec.get("user") else spec["host"] + + +def _manager(config): + if not isinstance(config, dict) or set(config) != MANAGER_FIELDS: + raise DeploymentError(f"manager requires exactly {sorted(MANAGER_FIELDS)}") + mode = config["mode"] + if mode not in ("embedded", "service"): + raise DeploymentError("manager.mode must be embedded or service") + service = mode == "service" + control_port = _positive_int(config["control_port"], "manager.control_port") + gateway_port = _positive_int(config["gateway_port"], "manager.gateway_port") + if control_port == gateway_port: + raise DeploymentError("manager.control_port and manager.gateway_port must differ") + if not 0 < control_port < 65536 or not 0 < gateway_port < 65536: + raise DeploymentError("manager ports must be in 1..65535") + control_url = _plain_origin(config["control_url"], "manager.control_url", + port=control_port, allow_loopback=not service) + gateway_url = _plain_origin(config["gateway_url"], "manager.gateway_url", + port=gateway_port, allow_loopback=not service) + return { + "id": _slug(config["id"], "manager.id"), "mode": mode, + "bind": _nonempty_str(config["bind"], "manager.bind"), + "control_port": control_port, "control_url": control_url, + "gateway_port": gateway_port, "gateway_url": gateway_url, + "run_token_file": _absolute_path(config["run_token_file"], "manager.run_token_file"), + "source_dir": _absolute_path(config["source_dir"], "manager.source_dir"), + "controller_python": _absolute_path(config["controller_python"], "manager.controller_python"), + } + + +def _gpu_target(config): + if not isinstance(config, dict) or set(config) != GPU_FIELDS: + raise DeploymentError(f"gpu_target requires exactly {sorted(GPU_FIELDS)}") + transport = config["transport"] + if transport not in TRANSPORTS: + raise DeploymentError(f"gpu_target.transport must be one of {TRANSPORTS}") + ssh = config["ssh"] + if transport == "ssh": + ssh = _ssh_spec(ssh, "gpu_target.ssh") + elif ssh is not None: + raise DeploymentError("gpu_target.ssh must be null for local transport") + target = { + "id": _slug(config["id"], "gpu_target.id"), "transport": transport, "ssh": ssh, + "source_dir": _absolute_path(config["source_dir"], "gpu_target.source_dir"), + "train_python": _absolute_path(config["train_python"], "gpu_target.train_python"), + "expected_gpus": _positive_int(config["expected_gpus"], "gpu_target.expected_gpus"), + } + environment_file = config["environment_file"] + if environment_file is not None: + target["environment_file"] = _absolute_path(environment_file, "gpu_target.environment_file") + return target + + +def _storage(config): + if not isinstance(config, dict) or set(config) != STORAGE_FIELDS: + raise DeploymentError(f"storage requires exactly {sorted(STORAGE_FIELDS)}") + if config["mode"] not in STORAGE_MODES: + raise DeploymentError(f"storage.mode must be one of {STORAGE_MODES}") + return { + "mode": config["mode"], + "run_root": _absolute_path(config["run_root"], "storage.run_root"), + "manager_writable": _bool(config["manager_writable"], "storage.manager_writable"), + "gpu_writable": _bool(config["gpu_writable"], "storage.gpu_writable"), + "worker_writable": _bool(config["worker_writable"], "storage.worker_writable"), + } + + +def _rollout(config): + if not isinstance(config, dict) or set(config) != ROLLOUT_FIELDS: + raise DeploymentError(f"rollout requires exactly {sorted(ROLLOUT_FIELDS)}") + if config["provider"] not in ROLLOUT_PROVIDERS: + raise DeploymentError(f"rollout.provider must be one of {ROLLOUT_PROVIDERS}") + workers = config["workers"] + # ssh_docker needs explicit machines; the embedded provider falls back to + # the training config's worker list, so it may start empty. + if (not isinstance(workers, list) + or (not workers and config["provider"] not in ("embedded", "sandbox"))): + raise DeploymentError("rollout.workers must be a nonempty list") + normalized, seen = [], set() + for worker in workers: + if not isinstance(worker, dict) or set(worker) != WORKER_FIELDS: + raise DeploymentError(f"rollout worker requires exactly {sorted(WORKER_FIELDS)}") + worker_id = _slug(worker["id"], "rollout worker id") + if worker_id in seen: + raise DeploymentError(f"duplicate rollout worker id: {worker_id}") + seen.add(worker_id) + task_roots = worker["task_roots"] + if (not isinstance(task_roots, list) or not task_roots + or any(not isinstance(root, str) or not root.strip() for root in task_roots)): + raise DeploymentError(f"worker {worker_id} requires nonempty task_roots") + normalized.append({ + "id": worker_id, + "ssh": _ssh_spec(worker["ssh"], f"rollout worker {worker_id} ssh"), + "source_dir": _absolute_path(worker["source_dir"], f"worker {worker_id} source_dir"), + "runner_python": _absolute_path(worker["runner_python"], f"worker {worker_id} runner_python"), + "docker": _bool(worker["docker"], f"worker {worker_id} docker"), + "task_roots": [_absolute_path(root, f"worker {worker_id} task_root") for root in task_roots], + }) + return {"provider": config["provider"], "workers": normalized} + + +def _connectivity(config): + if not isinstance(config, dict) or set(config) != CONNECTIVITY_FIELDS: + raise DeploymentError(f"connectivity requires exactly {sorted(CONNECTIVITY_FIELDS)}") + return { + "command_timeout_s": _positive_int(config["command_timeout_s"], "connectivity.command_timeout_s"), + "ssh_timeout_s": _positive_int(config["ssh_timeout_s"], "connectivity.ssh_timeout_s"), + "http_timeout_s": _positive_int(config["http_timeout_s"], "connectivity.http_timeout_s"), + "gpu_to_controller": _bool(config["gpu_to_controller"], "connectivity.gpu_to_controller"), + "worker_to_gateway": _bool(config["worker_to_gateway"], "connectivity.worker_to_gateway"), + "manager_to_gpu_inference": _bool(config["manager_to_gpu_inference"], "connectivity.manager_to_gpu_inference"), + } + + +def validate(config): + """Strictly validate and normalize a deployment mapping.""" + if not isinstance(config, dict): + raise DeploymentError("deployment config must be a mapping") + if set(config) != DEPLOYMENT_SECTIONS | {"schema_version"}: + raise DeploymentError( + "deployment config requires exactly schema_version and " + + ", ".join(sorted(DEPLOYMENT_SECTIONS))) + if config["schema_version"] != DEPLOYMENT_SCHEMA_VERSION or type(config["schema_version"]) is not int: + raise DeploymentError(f"deployment schema_version must be {DEPLOYMENT_SCHEMA_VERSION}") + return { + "schema_version": DEPLOYMENT_SCHEMA_VERSION, + "manager": _manager(config["manager"]), + "gpu_target": _gpu_target(config["gpu_target"]), + "storage": _storage(config["storage"]), + "rollout": _rollout(config["rollout"]), + "connectivity": _connectivity(config["connectivity"]), + } + + +def embedded_local(*, run_root=None, workers=(), expected_gpus=1): + """Default same-machine deployment; behavior matches the legacy pipeline.""" + return validate({ + "schema_version": 1, + "manager": {"id": "embedded-local", "mode": "embedded", "bind": "127.0.0.1", + "control_port": 49200, "control_url": "http://127.0.0.1:49200", + "gateway_port": 49184, "gateway_url": "http://127.0.0.1:49184", + "run_token_file": str(ROOT / "runs" / "run-token"), + "source_dir": str(ROOT), "controller_python": sys.executable}, + "gpu_target": {"id": "local", "transport": "local", "ssh": None, + "source_dir": str(ROOT), "train_python": sys.executable, + "environment_file": None, "expected_gpus": expected_gpus}, + "storage": {"mode": "shared_fs", "run_root": run_root or str(ROOT / "runs"), + "manager_writable": True, "gpu_writable": True, "worker_writable": True}, + "rollout": {"provider": "embedded", "workers": [ + {"id": f"worker-{index}", "ssh": {"host": worker}, "source_dir": str(ROOT), + "runner_python": sys.executable, "docker": True, "task_roots": [str(ROOT)]} + for index, worker in enumerate(workers)]}, + "connectivity": {"command_timeout_s": 30, "ssh_timeout_s": 15, "http_timeout_s": 10, + "gpu_to_controller": True, "worker_to_gateway": True, + "manager_to_gpu_inference": True}, + }) + + +DEFAULT_FILENAME = "deployment.yaml" + + +def load_deployment(path=None, *, default_directory="."): + """Load an explicit, default-discovered or embedded-local deployment config. + + Returns ``(config, source)`` where source describes where the config came + from; absent any file the embedded-local topology keeps legacy behavior. + """ + if path is not None: + source = str(Path(path).expanduser()) + candidates = [source] + else: + candidates = [str(Path(default_directory) / DEFAULT_FILENAME), DEFAULT_FILENAME] + source = candidates[0] + for candidate in candidates: + file = Path(candidate) + if not file.is_file(): + continue + try: + text = file.read_text() + except OSError as exc: + raise DeploymentError(f"cannot read deployment config {candidate}: {exc}") from exc + try: + raw = yaml.safe_load(text) + except yaml.YAMLError as exc: + raise DeploymentError(f"deployment YAML {candidate}: {exc}") from exc + return validate(raw), str(file.resolve()) + if path is not None: + raise DeploymentError(f"deployment config not found: {path}") + return embedded_local(), "embedded-local" + + +def summary(config, source): + """Plan-embedded deployment digest; the full config travels with it.""" + from harborrl.trajectories.native import digest + return { + "schema_version": DEPLOYMENT_SCHEMA_VERSION, + "source": source, + "digest": digest(config), + "mode": config["manager"]["mode"], + "manager_id": config["manager"]["id"], + "control_url": config["manager"]["control_url"], + "gateway_url": config["manager"]["gateway_url"], + "gpu": {"id": config["gpu_target"]["id"], "transport": config["gpu_target"]["transport"]}, + "worker_ids": [worker["id"] for worker in config["rollout"]["workers"]], + "run_root": config["storage"]["run_root"], + "config": config, + } diff --git a/harborrl/deployment/training_target.py b/harborrl/deployment/training_target.py new file mode 100644 index 000000000..4de5d2468 --- /dev/null +++ b/harborrl/deployment/training_target.py @@ -0,0 +1,217 @@ +"""TrainingTarget (P1): launch, query and cancel one Slime training job. + +``local`` keeps the legacy in-process launcher byte-for-byte. ``ssh`` runs the +same launcher script on the GPU machine through shared storage: the run +directory is written by the manager, executed by the GPU target, and polled +without holding an interactive session open. +""" +from __future__ import annotations + +import os +import shlex +import signal +import subprocess +import sys +import time +from pathlib import Path + +from harborrl.deployment import remote +from harborrl.deployment.schema import destination + +LAUNCHER_NAME = "gpu-launcher.sh" +EXIT_CODE_NAME = "exit-code" +PID_NAME = "launcher.pid" +STDOUT_NAME = "train-stdout.log" + + +class TrainingTargetError(RuntimeError): + pass + + +class LocalTrainingTarget: + """Legacy target: the Slime job runs as a child of this process.""" + + def __init__(self, command, *, cwd, env): + self.command = [str(part) for part in command] + self.cwd = str(cwd) + self.env = env + self.child = None + + def launch(self): + self.child = subprocess.Popen(self.command, cwd=self.cwd, env=self.env, + start_new_session=True) + return self.child.pid + + def wait(self): + if self.child is None: + raise TrainingTargetError("local target was never launched") + + def forward_signal(signum, frame): + if self.child.poll() is None: + try: + os.killpg(os.getpgid(self.child.pid), signum) + except ProcessLookupError: + pass + + previous = {sig: signal.signal(sig, forward_signal) + for sig in (signal.SIGINT, signal.SIGTERM)} + try: + return self.child.wait() + finally: + for sig, handler in previous.items(): + signal.signal(sig, handler) + + def query(self): + if self.child is None: + return {"state": "not-launched"} + return {"state": "running" if self.child.poll() is None else "exited", + "exit_code": self.child.returncode} + + def cancel(self): + if self.child is None or self.child.poll() is not None: + return False + try: + os.killpg(os.getpgid(self.child.pid), signal.SIGINT) + except (ProcessLookupError, PermissionError): + self.child.terminate() + return True + + +class SshTrainingTarget: + """Remote target over shared storage; the launcher script is the contract.""" + + def __init__(self, spec, *, source_dir, run_dir, environment_file=None, + command_timeout=60, ssh_timeout=15, poll_interval=2.0): + self.spec = dict(spec) + self.source_dir = str(source_dir) + self.run_dir = Path(run_dir).resolve() + self.environment_file = environment_file + self.command_timeout = command_timeout + self.ssh_timeout = ssh_timeout + self.poll_interval = poll_interval + self._launched = False + + def write_launcher(self, command, environment, *, path_rewrites=()): + """Materialize the launcher; env values and paths are quoted, never interpolated.""" + command = [str(part) for part in command] + for manager_path, target_path in path_rewrites: + command = [str(part).replace(str(manager_path), str(target_path), 1) + if str(part).startswith(str(manager_path)) else part for part in command] + lines = ["#!/usr/bin/env bash", + "# Generated by the HarborRL manager; executed on the GPU target.", + "set -uo pipefail", + f"cd {shlex.quote(self.source_dir)}", + f"export RUN_DIR={shlex.quote(str(self.run_dir))}"] + if self.environment_file: + # A controlled JSON mapping only; never an arbitrary remote shell. + quoted_file = shlex.quote(str(self.environment_file)) + lines.append( + "eval \"$(HARBORRL_ENV_FILE=" + quoted_file + " python3 - <<'HARBORRL_ENV_JSON'\n" + "import json, os, shlex\n" + "with open(os.environ['HARBORRL_ENV_FILE']) as stream:\n" + " for key, value in sorted(json.load(stream).items()):\n" + " print(f'export {key}={shlex.quote(str(value))}')\n" + "HARBORRL_ENV_JSON\n" + ")\"") + for key in sorted(environment): + lines.append(f"export {key}={shlex.quote(str(environment[key]))}") + lines.append("status=0") + # The command is a full argv (its first word is already the + # interpreter); execute it as-is and capture the real exit code. + lines.append(" ".join(shlex.quote(part) for part in command) + + " || status=$?") + lines.append(f"printf '%s\\n' \"$status\" > {shlex.quote(str(self.run_dir / EXIT_CODE_NAME))}") + lines.append("exit \"$status\"") + launcher = self.run_dir / LAUNCHER_NAME + launcher.write_text("\n".join(lines) + "\n") + launcher.chmod(0o755) + return launcher + + def launch(self): + if self._launched: + raise TrainingTargetError("ssh target was already launched") + # The background job records its own session-leader pid: setsid execs + # the launcher, so the recorded pid heads the cancellable process group. + bootstrap = (f"cd {shlex.quote(str(self.run_dir))} && " + f"(setsid nohup bash {LAUNCHER_NAME} > {STDOUT_NAME} 2>&1" + f" < /dev/null & echo $! > {PID_NAME})") + result = remote.run_ssh(self.spec, bootstrap, timeout=self.command_timeout, + connect_timeout=self.ssh_timeout) + if result.returncode != 0: + raise TrainingTargetError( + f"remote launch failed on {destination(self.spec)}: " + f"{result.stderr.strip()[-500:]}") + self._launched = True + return self.run_dir / PID_NAME + + def _pid(self): + try: + return int((self.run_dir / PID_NAME).read_text().strip()) + except (OSError, ValueError): + return None + + def exit_code(self): + try: + return int((self.run_dir / EXIT_CODE_NAME).read_text().strip()) + except (OSError, ValueError): + return None + + def alive(self): + pid = self._pid() + if pid is None: + return False + result = remote.run_ssh(self.spec, f"kill -0 {pid} 2>/dev/null && echo ok", + timeout=self.command_timeout, connect_timeout=self.ssh_timeout) + return result.returncode == 0 and result.stdout.strip() == "ok" + + def wait(self, *, timeout=None): + """Poll the shared exit-code record; never resubmit or restart.""" + deadline = None if timeout is None else time.monotonic() + timeout + while True: + code = self.exit_code() + if code is not None: + return code + if deadline is not None and time.monotonic() >= deadline: + raise TimeoutError(f"GPU job in {self.run_dir} did not finish in time") + time.sleep(self.poll_interval) + + def query(self): + code = self.exit_code() + return {"state": "exited" if code is not None else + ("running" if self.alive() else "unknown"), + "exit_code": code, "pid": self._pid(), + "run_dir": str(self.run_dir)} + + def cancel(self, *, grace_seconds=30): + pid = self._pid() + if pid is None: + return False + for signame in ("INT", "TERM", "KILL"): + remote.run_ssh(self.spec, f"kill -{signame} -- -{pid} 2>/dev/null || " + f"kill -{signame} {pid} 2>/dev/null || true", + timeout=self.command_timeout, connect_timeout=self.ssh_timeout) + deadline = time.monotonic() + grace_seconds + while time.monotonic() < deadline: + if self.exit_code() is not None: + return True + time.sleep(min(2.0, self.poll_interval)) + return self.exit_code() is not None + + +def local_target(plan, *, environment): + """Build the legacy in-process target from a resolved plan.""" + allowed = {"PATH", "HOME", "USER", "LANG", "LC_ALL", "TMPDIR", "LD_LIBRARY_PATH", + "CUDA_VISIBLE_DEVICES", "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", + "http_proxy", "https_proxy", "no_proxy", "PYTHONPATH", + "SLIME_DIR", "MEGATRON_DIR", "SGLANG_IMAGE"} + child_env = {key: value for key, value in os.environ.items() if key in allowed} + child_env["PATH"] = os.pathsep.join( + [str(Path(sys.executable).parent), child_env.get("PATH", os.defpath)]) + child_env["PYTHONPATH"] = os.pathsep.join(filter(None, [ + child_env.get("SLIME_DIR", ""), child_env.get("MEGATRON_DIR", ""), + str(Path(__file__).resolve().parents[2]), child_env.get("PYTHONPATH", ""), + ])) + child_env.update(environment) + from harborrl.config import ROOT + + return LocalTrainingTarget(plan["training_command"], cwd=ROOT, env=child_env) diff --git a/harborrl/gateway/messages.py b/harborrl/gateway/messages.py index 479e764eb..9c474817b 100644 --- a/harborrl/gateway/messages.py +++ b/harborrl/gateway/messages.py @@ -58,7 +58,7 @@ def convert(payload, *, model, max_output_tokens=8192, count_only=False): raise ProtocolError("unsupported context management control") effort = payload.get("output_config") if effort is not None and (not isinstance(effort, dict) or set(effort) != {"effort"} - or effort["effort"] not in ("low", "medium", "high")): + or effort["effort"] not in ("low", "medium", "high", "xhigh")): raise ProtocolError("unsupported output configuration") if not isinstance(payload.get("tools", []), list): raise ProtocolError("tools must be a list") @@ -138,7 +138,13 @@ def parse_qwen_output(text, *, response_id, tools): """Deterministic Qwen tool markers; malformed model output is never repaired.""" if "" in text or "" in text: raise ProtocolError("thinking output is not enabled by this profile") - names = {tool["function"]["name"] for tool in tools} + names = {} + for tool in tools: + name = tool["function"]["name"] + alias = name.casefold() + if alias in names: + raise ProtocolError("ambiguous generated tool alias") + names[alias] = name blocks, offset = [], 0 matches = list(re.finditer(r"\s*(.*?)\s*", text, re.DOTALL)) remainder = re.sub(r"\s*(.*?)\s*", "", text, flags=re.DOTALL) @@ -152,9 +158,17 @@ def parse_qwen_output(text, *, response_id, tools): except ValueError as exc: raise ProtocolError("invalid generated tool JSON") from exc keys(call, ("name", "arguments"), "generated tool") - if call.get("name") not in names or not isinstance(call.get("arguments"), dict): - raise ProtocolError("unknown generated tool or invalid arguments") - blocks.append({"type": "tool_use", "id": f"toolu_{response_id}_{index}", "name": call["name"], "input": call["arguments"]}) + generated_name = call.get("name") + if generated_name == "finish" and call.get("arguments") == {}: + if text[match.end():]: + raise ProtocolError("finish marker must be the final output") + return blocks + canonical_name = names.get(generated_name.casefold()) if isinstance(generated_name, str) else None + if canonical_name is None: + raise ProtocolError(f"unknown generated tool: {generated_name!r}") + if not isinstance(call.get("arguments"), dict): + raise ProtocolError("generated tool arguments are not an object") + blocks.append({"type": "tool_use", "id": f"toolu_{response_id}_{index}", "name": canonical_name, "input": call["arguments"]}) offset = match.end() if text[offset:] or not blocks: blocks.append({"type": "text", "text": text[offset:]}) diff --git a/harborrl/gateway/server.py b/harborrl/gateway/server.py index 282a95e26..fd011c505 100644 --- a/harborrl/gateway/server.py +++ b/harborrl/gateway/server.py @@ -110,7 +110,7 @@ def do_POST(self): gateway.registry.delivery(aid, message["id"], "delivery_unknown") status = 401 if isinstance(exc, PermissionError) else 400 if isinstance(exc, (ProtocolError, json.JSONDecodeError)) else 502 # Do not echo backend errors or request contents (credentials may appear there). - self.reply(status, {"type": "error", "error": {"type": "authentication_error" if status == 401 else "invalid_request_error" if status == 400 else "api_error", "message": type(exc).__name__}}) + self.reply(status, {"type": "error", "error": {"type": "authentication_error" if status == 401 else "invalid_request_error" if status == 400 else "api_error", "message": f"{type(exc).__name__}: {exc}"}}) server = ThreadingHTTPServer((host, port), Handler) server.daemon_threads = True diff --git a/harborrl/gateway/sglang_backend.py b/harborrl/gateway/sglang_backend.py index f37f594d8..d2efe5e63 100644 --- a/harborrl/gateway/sglang_backend.py +++ b/harborrl/gateway/sglang_backend.py @@ -9,24 +9,23 @@ from harborrl.trajectories.native import digest, finite -from .messages import ProtocolError, parse_qwen_output +from .messages import ProtocolError class SGLangBackend: - def __init__(self, endpoint, tokenizer, *, tokenizer_digest, template_digest, - timeout=120, max_context=32768, audited_raw_logprobs=False): + def __init__(self, endpoint, tokenizer, model_adapter, *, tokenizer_digest, + template_digest, timeout=120, max_context=32768, + audited_raw_logprobs=False): self.endpoint = endpoint.rstrip("/") self.tokenizer = tokenizer + self.model_adapter = model_adapter self.tokenizer_digest, self.template_digest = tokenizer_digest, template_digest self.timeout, self.max_context = timeout, max_context self.audited_raw_logprobs = audited_raw_logprobs self.opener = request.build_opener(request.ProxyHandler({})) def prepare(self, converted): - ids = self.tokenizer.apply_chat_template(converted["messages"], tools=converted["tools"] or None, - tokenize=True, add_generation_prompt=True, enable_thinking=False) - if not isinstance(ids, list) or not ids or any(type(i) is not int or i < 0 for i in ids): - raise ProtocolError("tokenizer did not produce actual input IDs") + ids = self.model_adapter.render_chat(self.tokenizer, converted) if len(ids) + converted["sampling"]["max_new_tokens"] > self.max_context: raise ProtocolError("context budget exceeded; implicit truncation is forbidden") return ids @@ -97,7 +96,9 @@ def decode(self, output, payload, converted, identity, response_id): finish = meta.get("finish_reason", {}) if finish.get("type") not in ("stop", "length"): raise ValueError("unsupported serving finish reason") - blocks = parse_qwen_output(text, response_id=response_id, tools=converted["tools"]) + blocks = self.model_adapter.parse_output( + text, response_id=response_id, tools=converted["tools"] + ) has_tools = any(b["type"] == "tool_use" for b in blocks) stop_sequence = finish.get("matched") if isinstance(finish.get("matched"), str) else None if stop_sequence not in converted["sampling"]["stop"]: diff --git a/harborrl/gateway/trace.py b/harborrl/gateway/trace.py index a5a5d3118..5f5ca7c6a 100644 --- a/harborrl/gateway/trace.py +++ b/harborrl/gateway/trace.py @@ -94,6 +94,19 @@ def delivery(self, aid, response_id, status): state["pending"].discard(matches[0]["request_id"]) self.changed.notify_all() + def wait_delivered(self, aid, timeout): + deadline = time.monotonic() + timeout + with self.changed: + state = self.attempts[aid] + while state["pending"]: + remaining = deadline - time.monotonic() + if remaining <= 0: + raise TimeoutError("policy delivery acknowledgement still pending") + self.changed.wait(remaining) + if any(turn.get("delivery") not in ("sent", "consumed_confirmed") + for turn in state["turns"]): + raise ValueError("policy response delivery was not confirmed") + def consume(self, aid, response_id, content, evidence_ref): """Called only by trusted native-session audit, never by an HTTP policy client.""" with self.lock: @@ -109,6 +122,13 @@ def consume(self, aid, response_id, content, evidence_ref): publish(state["root"] / "consumption" / f"{response_id}.json", {"response_id": response_id, "content_digest": digest(content), "evidence_ref": evidence_ref}) turn.update(delivery="consumed_confirmed", consumption_ref=evidence_ref) + def unconsumed_responses(self, aid): + with self.lock: + state = self.attempts[aid] + return [{"response_id": turn["response_id"], "content": deepcopy(turn["response"]["content"]), + "finish_reason": turn.get("finish_reason"), "is_final": index == len(state["turns"]) - 1} + for index, turn in enumerate(state["turns"]) if turn.get("delivery") != "consumed_confirmed"] + def close(self, aid): with self.lock: state = self.attempts[aid] diff --git a/harborrl/harnesses/__init__.py b/harborrl/harnesses/__init__.py new file mode 100644 index 000000000..53da87865 --- /dev/null +++ b/harborrl/harnesses/__init__.py @@ -0,0 +1 @@ +"""Agent harness adapters and their protocol boundaries.""" diff --git a/harborrl/harnesses/claude_code.py b/harborrl/harnesses/claude_code.py new file mode 100644 index 000000000..fb5c27519 --- /dev/null +++ b/harborrl/harnesses/claude_code.py @@ -0,0 +1,53 @@ +"""Claude Code harness adapter with explicit credentials and session audit.""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path + +from harborrl.rollout.harbor_job.audit import audit_session +from harborrl.rollout.harbor_job.bindings import ( + claude_config, + sanitized_runner_env, +) + + +class ClaudeCodeHarnessAdapter: + harness_id = "claude_code" + + def validate_profile(self, profile, gateway_url): + return claude_config(profile, gateway_url, "validation-only") + + def agent_config(self, profile, gateway_url, credential, max_output_tokens=8192): + return claude_config(profile, gateway_url, credential, max_output_tokens) + + def runner_environment(self, environment): + return sanitized_runner_env(environment) + + def audit_session(self, path, registry, attempt_id): + return audit_session(path, registry, attempt_id) + + def audit_empty_final(self, path, registry, attempt_id, audit): + """Claude 2.1.141 omits empty final messages from persisted sessions.""" + missing = registry.unconsumed_responses(attempt_id) + if not missing: + return + if len(missing) != 1 or missing[0] != { + "response_id": missing[0]["response_id"], "content": [], + "finish_reason": "end_turn", "is_final": True, + }: + raise ValueError("only one empty final end_turn may use terminal evidence") + path = Path(path) + raw = path.read_bytes() + events = [json.loads(line) for line in raw.splitlines() if line.strip()] + results = [event for event in events if event.get("type") == "result"] + if len(results) != 1 or events[-1] != results[0]: + raise ValueError("expected one final Claude result") + result = results[0] + expected = {"subtype": "success", "is_error": False, "result": "", + "stop_reason": "end_turn", "session_id": audit["session_id"], + "terminal_reason": "completed"} + if any(result.get(key) != value for key, value in expected.items()) or result.get("is_error") is not False: + raise ValueError("empty final response lacks successful matching session result") + evidence = f"{path}#sha256={hashlib.sha256(raw).hexdigest()}" + registry.consume(attempt_id, missing[0]["response_id"], [], evidence) diff --git a/harborrl/harnesses/offline_claude.py b/harborrl/harnesses/offline_claude.py new file mode 100644 index 000000000..cd44d37cd --- /dev/null +++ b/harborrl/harnesses/offline_claude.py @@ -0,0 +1,61 @@ +"""Pinned native Claude executable deployment, without package-manager fallback.""" +import asyncio +import hashlib +import json +import shlex +from pathlib import Path + +from harbor.agents.installed.claude_code import ClaudeCode + +from harborrl.trajectories.native import publish + + +def binary_checksum(binary): + with binary.open('rb') as source: + return hashlib.file_digest(source, 'sha256').hexdigest() + + +class OfflineClaudeCode(ClaudeCode): + def __init__(self, *args, installation, **kwargs): + self.installation = installation + super().__init__(*args, **kwargs) + + async def install(self, environment): + lock = self.installation + binary = Path(lock['binary']) + checksum = await asyncio.to_thread(binary_checksum, binary) + if checksum != lock['sha256']: + raise ValueError('offline Claude binary checksum mismatch') + # Harbor invokes Claude with this HOME-local prefix on every command. + probe = ('import json, os, platform; print(json.dumps(dict(' + 'system=platform.system(), machine=platform.machine(), ' + 'libc=os.confstr("CS_GNU_LIBC_VERSION"), home=os.environ.get("HOME", ""))))') + result = await environment.exec('python3 -c ' + shlex.quote(probe)) + try: + system = json.loads(result.stdout or '') + except ValueError: + system = {} + if (result.return_code or system.get('system') != 'Linux' or system.get('machine') != 'x86_64' + or not str(system.get('libc', '')).startswith('glibc ')): + raise ValueError('offline binary requires Linux x86_64 glibc and a writable HOME') + home = system.get('home', '') + if not home.startswith('/') or home == '/': + raise ValueError('invalid agent HOME') + target = home + '/.local/bin/claude' + staging = target + '.harborrl-upload' + result = await environment.exec('mkdir -p ' + shlex.quote(home + '/.local/bin')) + if result.return_code: + raise ValueError('agent tool directory is not writable') + await environment.upload_file(binary, staging) + command = (f'printf "%s %s\\n" {shlex.quote(lock["sha256"])} {shlex.quote(staging)} | sha256sum -c -' + f' && chmod 755 {shlex.quote(staging)} && mv {shlex.quote(staging)} {shlex.quote(target)}') + result = await environment.exec(command) + if result.return_code: + raise ValueError('offline Claude installation checksum/permissions failed') + result = await environment.exec(f'{shlex.quote(target)} --version') + version = (result.stdout or '').strip().split() + if result.return_code or not version or version[0] != lock['version']: + raise ValueError('offline Claude installation/version probe failed') + publish(self.logs_dir / 'offline-installation.json', { + 'version': lock['version'], 'sha256': lock['sha256'], 'target': target, + 'platform': system, 'mode': lock['mode']}) diff --git a/harborrl/math_registry.py b/harborrl/math_registry.py new file mode 100644 index 000000000..61f0a2b37 --- /dev/null +++ b/harborrl/math_registry.py @@ -0,0 +1,316 @@ +"""Explicit MATH component contracts and startup compatibility checks.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Protocol + +from harborrl.algorithms.grpo import GRPOAlgorithmAdapter +from harborrl.backends.slime_v032.versions import BACKEND_CONTRACT +from harborrl.harnesses.claude_code import ClaudeCodeHarnessAdapter +from harborrl.models.qwen import QwenModelAdapter +from harborrl.tasks.jsonl_provider import JsonlTaskProvider +from harborrl.tasks.provider import NativeTaskProvider + + +class ComponentRegistry: + def __init__(self, dimension): + self.dimension = dimension + self._components = {} + + def register(self, spec): + if spec.id in self._components: + raise ValueError(f"duplicate {self.dimension} registration: {spec.id}") + self._components[spec.id] = spec + return spec + + def get(self, component_id): + try: + return self._components[component_id] + except KeyError as exc: + raise ValueError( + f"unsupported {self.dimension}: {component_id}; " + f"registered: {', '.join(sorted(self._components))}" + ) from exc + + def items(self): + return tuple(self._components[spec_id] for spec_id in sorted(self._components)) + + +@dataclass(frozen=True) +class ModelSpec: + id: str + display_name: str + preset_prefix: str + serving_backend: str + tool_protocol: str + gateway_protocols: frozenset[str] + training_backend_contracts: frozenset[str] + adapter: QwenModelAdapter + version: str + evidence: tuple[str, ...] + limitations: tuple[str, ...] + + +@dataclass(frozen=True) +class AlgorithmSpec: + id: str + display_name: str + training_backend_contracts: frozenset[str] + minimum_group_size: int + requires_audited_raw_logprobs: bool + reduction: str + adapter: GRPOAlgorithmAdapter + backend_hooks: dict[str, str] + version: str + evidence: tuple[str, ...] + limitations: tuple[str, ...] + + +@dataclass(frozen=True) +class TaskProviderSpec: + id: str + display_name: str + execution_backends: frozenset[str] + task_format: str + adapter: TaskProviderAdapter + version: str + evidence: tuple[str, ...] + limitations: tuple[str, ...] + + +@dataclass(frozen=True) +class HarnessSpec: + id: str + display_name: str + request_protocol: str + tool_protocol: str + harbor_versions: frozenset[str] + adapter: ClaudeCodeHarnessAdapter + version: str + evidence: tuple[str, ...] + limitations: tuple[str, ...] + + +class ModelAdapter(Protocol): + def render_chat(self, tokenizer, converted): ... + + def parse_output(self, text, *, response_id, tools): ... + + +class AlgorithmAdapter(Protocol): + def compute_advantages_and_returns(self, args, rollout_data): ... + + +class TaskAdapter(Protocol): + def discover(self, tasks_dir): ... + + def inspect(self, path, *, expected_digest=None): ... + + def catalog(self, config): ... + + +class TaskProviderAdapter(Protocol): + def discover(self, tasks_dir): ... + + def inspect(self, path, *, expected_digest=None): ... + + def catalog(self, config): ... + + def prompt_rows(self, entries): ... + + +class HarnessAdapter(Protocol): + def validate_profile(self, profile, gateway_url): ... + + def agent_config(self, profile, gateway_url, credential, max_output_tokens=8192): ... + +QWEN_MODEL = ModelSpec( + id="qwen", + display_name="Qwen", + preset_prefix="qwen", + serving_backend="sglang", + tool_protocol="qwen-tool-call-v1", + gateway_protocols=frozenset({"anthropic-messages-v1"}), + training_backend_contracts=frozenset({BACKEND_CONTRACT}), + adapter=QwenModelAdapter(), + version="qwen3-profile-v1", + evidence=("examples/native/train_qwen_native.yaml",), + limitations=("preset must start with qwen", "tokenizer and template digests are checked live"), +) +GRPO_ALGORITHM = AlgorithmSpec( + id="grpo", + display_name="Slime GRPO", + training_backend_contracts=frozenset({BACKEND_CONTRACT}), + minimum_group_size=2, + requires_audited_raw_logprobs=True, + reduction="global-sum", + adapter=GRPOAlgorithmAdapter(), + backend_hooks={ + "rollout": "harborrl.backends.slime_v032.rollout.generate_rollout", + "converter": "harborrl.backends.slime_v032.converter.convert_samples_to_train_data", + "advantage": "harborrl.backends.slime_v032.advantage.compute_advantages_and_returns", + "loss": "harborrl.backends.slime_v032.loss.loss_function", + "postprocess": "harborrl.backends.slime_v032.postprocess.rollout_data_postprocess", + }, + version="native-centered-grpo-v1", + evidence=("harborrl/backends/slime_v032/loss.py",), + limitations=("requires complete reward-varied groups", "only Slime v0.3.2 hooks are accepted"), +) +HARBOR_JSONL_TASK_PROVIDER = TaskProviderSpec( + id="harbor_jsonl", + display_name="Harbor JSONL", + execution_backends=frozenset({"harbor_job"}), + task_format="native_single_step_jsonl_v1", + adapter=JsonlTaskProvider(), + version="jsonl-catalog-lock-v1", + evidence=("examples/native/catalog.jsonl",), + limitations=( + "one task lock must be encoded per JSONL line", + "tasks must be fetched before cataloging", + "task selectors use catalog IDs", + ), +) +NATIVE_TASK_PROVIDER = TaskProviderSpec( + id="native_harbor", + display_name="Harbor", + execution_backends=frozenset({"harbor_job"}), + task_format="native_single_step_text_v1", + adapter=NativeTaskProvider(), + version="catalog-lock-v1", + evidence=("examples/native/catalog.json",), + limitations=("tasks must be fetched before cataloging", "task selectors use catalog IDs"), +) +CLAUDE_CODE_HARNESS = HarnessSpec( + id="claude_code", + display_name="Claude Code", + request_protocol="anthropic-messages-v1", + tool_protocol="qwen-tool-call-v1", + harbor_versions=frozenset({"0.23.0"}), + adapter=ClaudeCodeHarnessAdapter(), + version="claude-code-session-audit-v1", + evidence=("harborrl/rollout/harbor_job/audit.py",), + limitations=("exact CLI version is required", "ambient credentials are rejected"), +) +class MATHRegistry: + def __init__(self): + self.models = ComponentRegistry("model") + self.algorithms = ComponentRegistry("algorithm") + self.tasks = ComponentRegistry("task provider") + self.harnesses = ComponentRegistry("harness") + self.models.register(QWEN_MODEL) + self.algorithms.register(GRPO_ALGORITHM) + self.tasks.register(NATIVE_TASK_PROVIDER) + self.tasks.register(HARBOR_JSONL_TASK_PROVIDER) + self.harnesses.register(CLAUDE_CODE_HARNESS) + + def model_for_preset(self, preset): + for spec in self.models.items(): + if str(preset).startswith(spec.preset_prefix): + return spec + raise ValueError( + f"unsupported model preset: {preset}; " + f"registered: {', '.join(spec.id for spec in self.models.items())}" + ) + + def algorithm_for_contract(self, contract): + for spec in self.algorithms.items(): + if contract in spec.training_backend_contracts: + return spec + raise ValueError(f"no registered algorithm supports backend contract: {contract}") + + +MATH_REGISTRY = MATHRegistry() + + +@dataclass(frozen=True) +class MATHSelection: + model: ModelSpec + algorithm: AlgorithmSpec + task: TaskProviderSpec + harness: HarnessSpec + + def asdict(self): + return { + "model": _describe(self.model), + "algorithm": _describe(self.algorithm), + "task": _describe(self.task), + "harness": _describe(self.harness), + } + + +def _describe(spec): + return { + "id": spec.id, + "display_name": spec.display_name, + "version": spec.version, + "evidence": list(spec.evidence), + "limitations": list(spec.limitations), + } + + +def select_math(config): + return MATHSelection( + model=MATH_REGISTRY.model_for_preset(config["model"]["args_file"]), + algorithm=MATH_REGISTRY.algorithm_for_contract( + config["training"]["backend_contract"] + ), + task=MATH_REGISTRY.tasks.get( + "harbor_jsonl" + if str(config.get("tasks", {}).get("catalog", "")).endswith(".jsonl") + else "native_harbor" + ), + harness=MATH_REGISTRY.harnesses.get(config["harness"]["name"]), + ) + + +def validate_combination(selection, config): + contract = config["training"]["backend_contract"] + if contract not in selection.model.training_backend_contracts: + raise ValueError( + f"model {selection.model.id} does not support training backend {contract}" + ) + if contract not in selection.algorithm.training_backend_contracts: + raise ValueError( + f"algorithm {selection.algorithm.id} does not support training backend {contract}" + ) + if config["execution"]["backend"] not in selection.task.execution_backends: + raise ValueError( + f"task provider {selection.task.id} does not support execution backend " + f"{config['execution']['backend']}" + ) + if config["harbor"]["version"] not in selection.harness.harbor_versions: + raise ValueError( + f"harness {selection.harness.id} does not support Harbor " + f"{config['harbor']['version']}" + ) + if selection.model.tool_protocol != selection.harness.tool_protocol: + raise ValueError("model and harness tool protocols do not match") + if selection.harness.request_protocol not in selection.model.gateway_protocols: + raise ValueError( + f"model gateway does not support harness protocol {selection.harness.request_protocol}" + ) + if config["sampling"]["group_size"] < selection.algorithm.minimum_group_size: + raise ValueError( + f"algorithm {selection.algorithm.id} requires group size >= " + f"{selection.algorithm.minimum_group_size}" + ) + if ( + selection.algorithm.requires_audited_raw_logprobs + and not config["gateway"]["audited_raw_logprobs"] + ): + raise ValueError( + f"algorithm {selection.algorithm.id} requires audited raw logprobs" + ) + return selection + + +def list_components(): + return { + dimension: [_describe(spec) for spec in registry.items()] + for dimension, registry in ( + ("models", MATH_REGISTRY.models), + ("algorithms", MATH_REGISTRY.algorithms), + ("tasks", MATH_REGISTRY.tasks), + ("harnesses", MATH_REGISTRY.harnesses), + ) + } diff --git a/harborrl/models/__init__.py b/harborrl/models/__init__.py new file mode 100644 index 000000000..89314d47e --- /dev/null +++ b/harborrl/models/__init__.py @@ -0,0 +1 @@ +"""Model family adapters used by serving and training boundaries.""" diff --git a/harborrl/models/qwen.py b/harborrl/models/qwen.py new file mode 100644 index 000000000..cbd096dad --- /dev/null +++ b/harborrl/models/qwen.py @@ -0,0 +1,39 @@ +"""Qwen tokenizer/template and generated tool-call protocol.""" +from __future__ import annotations + +from harborrl.gateway.messages import ProtocolError, parse_qwen_output + + +def normalize_chat_template_ids(rendered): + """Accept both legacy lists and modern ``BatchEncoding`` results.""" + if hasattr(rendered, "get"): + rendered = rendered.get("input_ids") + if isinstance(rendered, (list, tuple)): + if rendered and isinstance(rendered[0], (list, tuple)): + rendered = rendered[0] + return list(rendered) + return rendered + + +class QwenModelAdapter: + model_id = "qwen" + + def render_chat(self, tokenizer, converted): + rendered_ids = tokenizer.apply_chat_template( + converted["messages"], + tools=converted["tools"] or None, + tokenize=True, + add_generation_prompt=True, + enable_thinking=False, + ) + token_ids = normalize_chat_template_ids(rendered_ids) + if ( + not isinstance(token_ids, list) + or not token_ids + or any(type(token_id) is not int or token_id < 0 for token_id in token_ids) + ): + raise ProtocolError("tokenizer did not produce actual input IDs") + return token_ids + + def parse_output(self, text, *, response_id, tools): + return parse_qwen_output(text, response_id=response_id, tools=tools) diff --git a/harborrl/options/__init__.py b/harborrl/options/__init__.py new file mode 100644 index 000000000..a53f7a1cc --- /dev/null +++ b/harborrl/options/__init__.py @@ -0,0 +1,11 @@ +"""Offline configuration discovery and generation; no optional runtime imports.""" + + +def build_catalog(): + from .catalog import build_catalog as build + return build() + + +def build_config_bundle(answers, *, output_directory): + from .wizard import build_config_bundle as build + return build(answers, output_directory=output_directory) diff --git a/harborrl/options/catalog.py b/harborrl/options/catalog.py new file mode 100644 index 000000000..76ede0794 --- /dev/null +++ b/harborrl/options/catalog.py @@ -0,0 +1,147 @@ +"""JSON-safe option metadata. Existing validators remain authoritative.""" +from copy import deepcopy +from importlib.resources import files + +import yaml + +from harborrl.config import ROOT +from harborrl.config.native import FIELDS, OPTIONAL +from harborrl.deployment import schema +from harborrl.math_registry import MATH_REGISTRY + +from .presets import PRESETS + +TRAINING_FIELDS = { + "consistency.mode": ("string", "off, audit or enforce"), + "consistency.max_abs_delta": ("number_or_null", "Explicit deployment maximum token log-probability delta"), + "consistency.max_mean_abs_delta": ("number_or_null", "Explicit deployment mean token delta"), + "consistency.bad_token_delta": ("number_or_null", "Threshold identifying a bad token"), + "consistency.max_bad_fraction": ("number", "Allowed bad-token fraction between zero and one"), + "consistency.worst_tokens": ("integer", "Maximum diagnostic tokens, zero through 32"), + "execution.backend": ("string", "Task execution backend; currently harbor_job"), + "tasks.catalog": ("path", "Existing JSON/JSONL Harbor task lock; resolved on the controller"), + "harness.name": ("string", "Registered agent harness; currently claude_code"), + "harness.profile": ("path", "Pinned harness profile JSON on the controller"), + "harbor.python": ("path", "Runner interpreter on the local runner or SSH worker"), + "harbor.version": ("string", "Harbor version supported by the selected harness"), + "harbor.workers": ("array", "Ordered SSH destinations; empty with a local runner"), + "harbor.runner_transport": ("string", "local or ssh; local requires no workers"), + "harbor.environment": ("object", "Docker runtime, or Sandbox runtime with a config file reference"), + "gateway.host": ("string", "Gateway listener address, distinct from its advertised origin"), + "gateway.port": ("integer", "Gateway listener port, 1 through 65535"), + "gateway.advertised_url": ("string", "Plain HTTP(S) origin reachable by runners; port must match"), + "gateway.tokenizer_digest": ("string", "SHA-256 of the actual tokenizer fingerprint; never a placeholder"), + "gateway.template_digest": ("string", "SHA-256 of the actual chat-template fingerprint"), + "gateway.audited_raw_logprobs": ("boolean", "Raw serving probability audit, required by GRPO"), + "model.checkpoint": ("path", "Student model checkpoint; must be accessible on the GPU target"), + "model.reference": ("path", "Reference checkpoint accessible on the GPU target"), + "model.args_file": ("string", "Supported Slime preset name; currently the qwen prefix"), + "training.num_rollout": ("integer", "Positive number of rollout batches"), + "training.learning_rate": ("number", "Positive finite learning rate"), + "training.save_interval": ("integer", "Positive checkpoint interval in rollout batches"), + "training.backend_contract": ("string", "Pinned backend/algorithm contract"), + "sampling.group_size": ("integer", "Samples per prompt, at least two for GRPO"), + "sampling.groups_per_batch": ("integer", "Positive number of complete groups per batch"), + "sampling.max_attempts": ("integer", "Bounded invalid-attempt retries; not low-reward retries"), + "sampling.max_tokens": ("integer", "Positive response limit below max_context"), + "sampling.max_context": ("integer", "Total context token limit"), + "deployment.layout": ("string", "split or colocate; controls actor/serving GPU allocation"), + "deployment.num_gpus": ("integer", "Total positive GPU budget, not a memory-fit guarantee"), + "deployment.actor_gpus": ("integer", "Actor GPU count; must be divisible by actor TP"), + "deployment.rollout_gpus": ("integer", "Serving GPU count; must be divisible by engine TP"), + "deployment.actor_tensor_parallel_size": ("integer", "Positive actor tensor-parallel size"), + "deployment.rollout_gpus_per_engine": ("integer", "Positive serving tensor-parallel size"), + "output.root": ("path", "Run output root; service mode must match shared storage.run_root"), +} + +DEPLOYMENT_GROUPS = { + "manager": schema.MANAGER_FIELDS, "gpu_target": schema.GPU_FIELDS, + "storage": schema.STORAGE_FIELDS, "rollout": schema.ROLLOUT_FIELDS, + "rollout.workers[]": schema.WORKER_FIELDS, "gpu_target.ssh": schema.SSH_FIELDS, + "rollout.workers[].ssh": schema.SSH_FIELDS, "connectivity": schema.CONNECTIVITY_FIELDS, +} +DEPLOYMENT_FIELDS = { + "id": ("string", "Short role identifier"), "mode": ("string", "Manager mode or shared storage mode"), + "bind": ("string", "Manager listener address"), + "control_port": ("integer", "Control listener port; different from gateway_port"), + "control_url": ("string", "Control origin reachable from the GPU target"), + "gateway_port": ("integer", "Gateway listener port"), + "gateway_url": ("string", "Gateway origin reachable from runners or the configured relay"), + "run_token_file": ("path", "Manager run-token file reference; never the secret value"), + "source_dir": ("path", "Absolute source checkout path on this role's machine"), + "controller_python": ("path", "Absolute interpreter path on the controller"), + "transport": ("string", "GPU launch transport: local or ssh"), + "ssh": ("object", "SSH specification; GPU local transport requires null"), + "train_python": ("path", "Absolute interpreter path on the GPU target"), + "environment_file": ("path_or_null", "Controlled JSON environment file on the GPU target"), + "expected_gpus": ("integer", "Expected target GPU inventory, at least the requested budget"), + "run_root": ("path", "Absolute shared run root visible to all participating roles"), + "manager_writable": ("boolean", "Declared shared-root access; verified by doctor"), + "gpu_writable": ("boolean", "Declared GPU shared-root access; verified by doctor"), + "worker_writable": ("boolean", "Declared worker shared-root access; verified by doctor"), + "provider": ("string", "Rollout placement/provider; not a dataset or algorithm"), + "workers": ("array", "Ordered worker role specifications; empty for local Sandbox runners"), + "runner_python": ("path", "Absolute interpreter path on the worker"), + "docker": ("boolean", "Worker Docker requirement"), + "task_roots": ("array", "Absolute task roots visible on this worker"), + "host": ("string", "SSH alias/destination, not an automatically verified host"), + "user": ("string", "Optional SSH username"), "port": ("integer", "Optional SSH port"), + "identity": ("path", "Optional identity-file reference; no private-key contents"), + "command_timeout_s": ("integer", "Positive remote command timeout"), + "ssh_timeout_s": ("integer", "Positive SSH connection timeout"), + "http_timeout_s": ("integer", "Positive HTTP check timeout"), + "gpu_to_controller": ("boolean", "Declared GPU-to-controller connectivity requirement"), + "worker_to_gateway": ("boolean", "Declared runner-to-gateway connectivity requirement"), + "manager_to_gpu_inference": ("boolean", "Declared controller-to-serving connectivity requirement"), +} + + +def _describe(spec, attributes): + record = {} + for key in ("id", "display_name", "version", "evidence", "limitations", *attributes): + value = getattr(spec, key) + record[key] = sorted(value) if isinstance(value, frozenset) else list(value) if isinstance(value, tuple) else value + return record + + +def dataset_metadata(): + return yaml.safe_load(files("harborrl.options").joinpath("resources/datasets.yaml").read_text(encoding="utf-8")) + + +def build_catalog(): + training = {} + for section, fields in sorted(FIELDS.items()): + for name in sorted(fields): + key = f"{section}.{name}" + kind, description = TRAINING_FIELDS[key] + training[key] = {"type": kind, "description": description, + "required": name not in OPTIONAL.get(section, set())} + deployment = {} + for section, fields in sorted(DEPLOYMENT_GROUPS.items()): + for name in sorted(fields): + kind, description = DEPLOYMENT_FIELDS[name] + deployment[f"{section}.{name}"] = {"type": kind, "description": description} + for key, values in {"manager.mode": ("embedded", "service"), "storage.mode": schema.STORAGE_MODES, + "rollout.provider": schema.ROLLOUT_PROVIDERS, "gpu_target.transport": schema.TRANSPORTS}.items(): + deployment[key]["values"] = list(values) + datasets = deepcopy(dataset_metadata()["datasets"]) + for item in datasets: + path = ROOT / item["task_root"] + item["available"] = item["status"] != "planned" and item["source"] == "bundled" and path.is_dir() + item["availability_note"] = "Provide an existing inspected task catalog; no download is performed" + registry = MATH_REGISTRY + return { + "catalog_version": 1, "schema_versions": {"training": 2, "deployment": 1}, + "models": [_describe(s, ("preset_prefix", "serving_backend", "tool_protocol", "gateway_protocols", "training_backend_contracts")) for s in registry.models.items()], + "algorithms": [_describe(s, ("training_backend_contracts", "minimum_group_size", "requires_audited_raw_logprobs", "reduction")) for s in registry.algorithms.items()], + "task_providers": [_describe(s, ("execution_backends", "task_format")) for s in registry.tasks.items()], + "harnesses": [_describe(s, ("request_protocol", "tool_protocol", "harbor_versions")) for s in registry.harnesses.items()], + "datasets": datasets, "deployment_presets": deepcopy(list(PRESETS)), + "fields": {"training": training, "deployment": deployment}, + "compatibility": {"scope": "component_metadata", "status": "unknown_until_configuration_validated", + "constraints": ["Model and harness tool/request protocols must match", + "Algorithm and model must support the selected backend contract", + "Task provider must support the execution backend", + "Harbor version must be supported by the harness"], + "validator": "harborrl.math_registry.validate_combination"}, + } diff --git a/harborrl/options/cli.py b/harborrl/options/cli.py new file mode 100644 index 000000000..7438628dc --- /dev/null +++ b/harborrl/options/cli.py @@ -0,0 +1,184 @@ +"""Terminal interface for the offline catalog, validation and bundle builder.""" +import argparse +import json +import shlex +import sys +from pathlib import Path + +import yaml + +from .catalog import build_catalog +from .presets import PRESETS, worker_defaults +from .validation import read_mapping, validate_paths +from .wizard import build_config_bundle, publish_bundle + +DIMENSIONS = {"models": "models", "algorithms": "algorithms", "tasks": "task_providers", + "harnesses": "harnesses", "datasets": "datasets", "deployments": "deployment_presets", + "fields": "fields", "compat": "compatibility"} + + +def ask(label, default=None, convert=str): + while True: + value = input(f"{label}" + (f" [{default}]" if default is not None else "") + ": ").strip() + if not value and default is not None: + return default + try: + if not value: + raise ValueError("A value is required") + return convert(value) + except ValueError as exc: + print(str(exc), file=sys.stderr) + + +def choose(label, choices): + for index, value in enumerate(choices, 1): + print(f" {index}. {value}") + while True: + index = ask(label, 1, int) + if 1 <= index <= len(choices): + return choices[index - 1] + print("Choose a listed number", file=sys.stderr) + + +def interactive_answers(): + preset = choose("Deployment", [p["id"] for p in PRESETS]) + t, d = {}, {} + service = preset != "embedded-local" + print("Components: Qwen / GRPO / Harbor tasks / Claude Code. Advanced fields use an answers file.") + if service: + d["manager"] = { + "source_dir": ask("Controller source directory"), + "controller_python": ask("Controller Python", sys.executable), + "control_url": ask("Controller origin (include port 49200)"), + "gateway_url": ask("Gateway origin (include port 49184)"), + } + d["storage"] = {"run_root": ask("Shared run root (absolute)")} + d["gpu_target"] = {"ssh": {"host": ask("GPU SSH alias/destination")}, + "source_dir": ask("GPU source directory"), "train_python": ask("GPU training Python")} + else: + d["gpu_target"] = {"train_python": ask("Training Python", sys.executable)} + if preset == "service-ssh": + workers = [] + count = ask("Number of SSH runners", 1, int) + if count < 1: + raise ValueError("At least one SSH runner is required") + for index in range(count): + worker = worker_defaults() + worker.update(id=f"worker-{index}", ssh={"host": ask(f"Runner {index} SSH destination")}, + source_dir=ask("Runner source directory"), runner_python=ask("Runner Python"), + task_roots=[ask("Runner task root")]) + workers.append(worker) + d["rollout"] = {"workers": workers} + else: + t["harbor"] = {"python": ask("Local Harbor runner Python", sys.executable)} + t["tasks"] = {"catalog": ask("Existing task catalog JSON/JSONL")} + t["harness"] = {"profile": ask("Existing harness profile JSON")} + t["model"] = {"checkpoint": ask("GPU checkpoint directory"), "reference": ask("GPU reference directory"), + "args_file": ask("Qwen model args preset", "qwen3-8B")} + t["gateway"] = {"tokenizer_digest": ask("Actual tokenizer SHA-256"), + "template_digest": ask("Actual template SHA-256")} + if not service: + t["gateway"].update(host=ask("Gateway bind address", "127.0.0.1"), + advertised_url=ask("Gateway origin reachable by tasks", "http://127.0.0.1:49184")) + layout = choose("GPU layout", ["colocate", "split"]) + t["deployment"] = {"layout": layout, "num_gpus": ask("Total GPU budget", 1, int)} + if layout == "split": + t["deployment"].update(actor_gpus=ask("Actor GPUs", 1, int), rollout_gpus=ask("Serving GPUs", 1, int)) + t["training"] = {"num_rollout": ask("Rollout batches", 2, int)} + t["sampling"] = {"group_size": ask("Samples per group", 2, int), + "groups_per_batch": ask("Groups per batch", 1, int)} + answers = {"answers_version": 1, "preset": preset, "training": t, "deployment": d} + if preset == "service-sandbox-local-runner": + answers["sandbox_config"] = ask("Existing Sandbox configuration") + return answers + + +def render_validation(result): + print(f"{result['status']} (scope: {result['scope']})") + for row in result["checks"]: + print(f" {row['status']}: {row['path']}: {row['message']}") + + +def render_catalog(result): + print("HarborRL options (catalog 1; training schema 2; deployment schema 1)") + for name, value in result.items(): + if name in {"catalog_version", "schema_versions"}: + continue + if name == "fields" and len(result) > 3: + print("fields: use 'harborrl options fields' for field descriptions") + elif isinstance(value, list): + print(f"{name}:") + for row in value: + description = row.get("description", row.get("display_name", "")) + if "available" in row: + description += f" (status={row['status']}, locally_available={row['available']})" + print(f" {row.get('id', row.get('name'))}: {description}") + else: + print(yaml.safe_dump({name: value}, sort_keys=False).rstrip()) + + +def main(argv): + parser = argparse.ArgumentParser(prog="harborrl") + commands = parser.add_subparsers(dest="command", required=True) + options = commands.add_parser("options", help="List supported configuration options") + options.add_argument("dimension", nargs="?", choices=DIMENSIONS) + options.add_argument("--json", action="store_true") + validate = commands.add_parser("validate", help="Validate offline; no runtime is started") + validate.add_argument("config") + validate.add_argument("--deployment") + validate.add_argument("--check-files", action="store_true") + validate.add_argument("--json", action="store_true") + init = commands.add_parser("init", help="Create a configuration bundle in a new directory") + init.add_argument("--out-dir", required=True) + init.add_argument("--answers") + init.add_argument("--probe-local", action="store_true") + args = parser.parse_args(argv) + try: + if args.command == "options": + result = build_catalog() + if args.dimension: + key = DIMENSIONS[args.dimension] + result = {k: result[k] for k in ("catalog_version", "schema_versions", key)} + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + else: + render_catalog(result) + return 0 + if args.command == "validate": + result = validate_paths(args.config, args.deployment, check_files=args.check_files) + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + else: + render_validation(result) + return int(result["status"] == "invalid") + if not args.answers and not sys.stdin.isatty(): + parser.error("init requires --answers when stdin is not a terminal") + output = Path(args.out_dir).expanduser().absolute() + if output.exists() or output.is_symlink(): + raise ValueError(f"Output directory already exists: {output}") + if args.probe_local: + from .probe import probe_local + print(json.dumps(probe_local(output), indent=2)) + answers = read_mapping(args.answers) if args.answers else interactive_answers() + bundle = build_config_bundle(answers, output_directory=output) + render_validation(bundle["validation"]) + if not args.answers: + print(bundle["files"]["config.yaml"]) + print(bundle["files"]["deployment.yaml"]) + if ask("Publish these files? (yes/no)", "no").lower() != "yes": + print("Cancelled; no files written") + return 0 + publish_bundle(bundle, output) + print(f"Created configuration bundle: {output}") + config = shlex.quote(str(output / "config.yaml")) + deployment = shlex.quote(str(output / "deployment.yaml")) + for command, suffix in (("validate", " --check-files"), ("train", " --dry-run"), ("doctor", "")): + print(f"harborrl {command} {config} --deployment {deployment}{suffix}") + print("doctor checks the live environment and connectivity; training requires a separate command.") + return 0 + except (ValueError, TypeError, KeyError, AttributeError, OSError, yaml.YAMLError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + except (EOFError, KeyboardInterrupt): + print("Cancelled; no configuration published", file=sys.stderr) + return 130 diff --git a/harborrl/options/presets.py b/harborrl/options/presets.py new file mode 100644 index 000000000..6cc7ef8b5 --- /dev/null +++ b/harborrl/options/presets.py @@ -0,0 +1,153 @@ +"""Small configuration recipes; final validity belongs to existing schemas.""" +import sys +from copy import deepcopy +from pathlib import Path + +from harborrl.config import ROOT +from harborrl.consistency.config import DEFAULTS as CONSISTENCY_DEFAULTS +from harborrl.deployment.schema import destination, embedded_local + +PRESETS = ( + {"id": "embedded-local", "description": "Local training and a local Harbor runner"}, + {"id": "service-ssh", "description": "Controller, SSH GPU target and SSH Docker runners"}, + {"id": "service-sandbox-local-runner", "description": "Controller and runner here; GPU target over SSH; Sandbox execution"}, +) + + +def merge(base, override, path=""): + if not isinstance(override, dict): + raise TypeError(f"{path or 'answers'} must be a mapping") + result = deepcopy(base) + for key, value in override.items(): + if key not in base: + raise ValueError(f"unknown field: {path}{key}") + result[key] = (merge(base[key], value, f"{path}{key}.") + if isinstance(base[key], dict) and base[key] else deepcopy(value)) + return result + + +def defaults(preset, output_directory): + if preset not in {item["id"] for item in PRESETS}: + raise ValueError(f"unknown preset: {preset}") + output = Path(output_directory).resolve() + service = preset != "embedded-local" + sandbox = preset == "service-sandbox-local-runner" + training = { + "consistency": deepcopy(CONSISTENCY_DEFAULTS), + "schema_version": 2, + "execution": {"backend": "harbor_job"}, "tasks": {"catalog": None}, + "harness": {"name": "claude_code", "profile": None}, + "harbor": {"python": None, "version": "0.23.0", "workers": [], + "runner_transport": "ssh" if preset == "service-ssh" else "local", + "environment": {"runtime": "sandbox", "config": None} if sandbox else {"runtime": "docker"}}, + "gateway": {"host": "0.0.0.0" if service else "127.0.0.1", "port": 49184, + "advertised_url": None if service else "http://127.0.0.1:49184", + "tokenizer_digest": None, "template_digest": None, "audited_raw_logprobs": True}, + "model": {"checkpoint": None, "reference": None, "args_file": "qwen3-8B"}, + "training": {"num_rollout": 2, "learning_rate": 1e-6, "save_interval": 1, + "backend_contract": "slime-v0.3.2-native-v1"}, + "sampling": {"group_size": 2, "groups_per_batch": 1, "max_attempts": 1, + "max_tokens": 2048, "max_context": 32768}, + "deployment": {"layout": "colocate", "num_gpus": 1, "actor_gpus": 1, + "rollout_gpus": 1, "actor_tensor_parallel_size": 1, "rollout_gpus_per_engine": 1}, + "output": {"root": str(output / "runs")}, + } + deployment = embedded_local(run_root=str(output / "runs")) + deployment["gpu_target"]["environment_file"] = None + deployment["manager"]["run_token_file"] = str(output / "run-token") + if service: + deployment["manager"].update(id="controller", mode="service", bind="0.0.0.0", + control_url=None, gateway_url=None) + deployment["gpu_target"].update(id="gpu", transport="ssh", ssh={"host": None, "user": None, "port": None, "identity": None}, + source_dir=None, train_python=None) + deployment["rollout"]["provider"] = "sandbox" if sandbox else "ssh_docker" + return training, deployment + + +def build_configs(answers, output_directory): + if not isinstance(answers, dict) or set(answers) - { + "answers_version", "preset", "training", "deployment", "sandbox_config"}: + raise ValueError("unknown answers field or non-mapping answers") + if type(answers.get("answers_version")) is not int or answers["answers_version"] != 1: + raise ValueError("answers_version must be 1") + preset = answers.get("preset") + training, deployment = defaults(preset, output_directory) + t = answers.get("training", {}) + d = answers.get("deployment", {}) + training = merge(training, t, "training.") + deployment = merge(deployment, d, "deployment.") + + # Repeated fields have one source unless both were explicitly supplied. + for section, field, other_section, other_field in ( + ("gateway", "host", "manager", "bind"), + ("gateway", "port", "manager", "gateway_port"), + ("gateway", "advertised_url", "manager", "gateway_url"), + ("output", "root", "storage", "run_root"), + ): + if field in t.get(section, {}) and other_field not in d.get(other_section, {}): + deployment[other_section][other_field] = training[section][field] + elif other_field in d.get(other_section, {}) and field not in t.get(section, {}): + training[section][field] = deployment[other_section][other_field] + elif field in t.get(section, {}) and other_field in d.get(other_section, {}): + left, right = training[section][field], deployment[other_section][other_field] + if field == "root" and isinstance(left, str) and isinstance(right, str): + left, right = ((Path(output_directory) / value).resolve() for value in (left, right)) + elif field == "advertised_url" and isinstance(left, str) and isinstance(right, str): + left, right = left.rstrip("/"), right.rstrip("/") + if left != right: + raise ValueError(f"training.{section}.{field} conflicts with deployment.{other_section}.{other_field}") + count = training["deployment"]["num_gpus"] + if "expected_gpus" not in d.get("gpu_target", {}): + deployment["gpu_target"]["expected_gpus"] = count + if training["deployment"]["layout"] == "colocate": + for field in ("actor_gpus", "rollout_gpus"): + if field not in t.get("deployment", {}): + training["deployment"][field] = count + if training["harbor"]["runner_transport"] == "ssh": + workers = deployment["rollout"]["workers"] + if workers: + if "workers" not in t.get("harbor", {}): + training["harbor"]["workers"] = [destination(w["ssh"]) for w in workers] + if "python" not in t.get("harbor", {}): + training["harbor"]["python"] = workers[0]["runner_python"] + if answers.get("sandbox_config") is not None: + if preset != "service-sandbox-local-runner": + raise ValueError("sandbox_config requires the Sandbox preset") + environment = training["harbor"]["environment"] + if environment.get("config") not in (None, answers["sandbox_config"]): + raise ValueError("sandbox_config conflicts with training.harbor.environment.config") + environment["config"] = answers["sandbox_config"] + required = [("training", section, field) for section, field in ( + ("tasks", "catalog"), ("harness", "profile"), ("harbor", "python"), + ("model", "checkpoint"), ("model", "reference"), + ("gateway", "tokenizer_digest"), ("gateway", "template_digest"), ("gateway", "advertised_url"))] + if preset != "embedded-local": + required += [("deployment", section, field) for section, field in ( + ("manager", "control_url"), ("manager", "gateway_url"), + ("gpu_target", "source_dir"), ("gpu_target", "train_python"))] + configs = {"training": training, "deployment": deployment} + missing = [".".join(keys) for keys in required if configs[keys[0]][keys[1]][keys[2]] in (None, "")] + if preset != "embedded-local" and not deployment["gpu_target"]["ssh"].get("host"): + missing.append("deployment.gpu_target.ssh.host") + if preset == "service-ssh" and not deployment["rollout"]["workers"]: + missing.append("deployment.rollout.workers") + if preset == "service-sandbox-local-runner" and not training["harbor"]["environment"].get("config"): + missing.append("sandbox_config") + if missing: + raise ValueError("Missing required answers: " + ", ".join(missing)) + for field in ("tokenizer_digest", "template_digest"): + if training["gateway"][field] == "0" * 64: + raise ValueError(f"gateway.{field}: a placeholder digest cannot be used by init") + expected_mode = "embedded" if preset == "embedded-local" else "service" + expected_provider = {"embedded-local": "embedded", "service-ssh": "ssh_docker", + "service-sandbox-local-runner": "sandbox"}[preset] + if deployment["manager"]["mode"] != expected_mode or deployment["rollout"]["provider"] != expected_provider: + raise ValueError("deployment mode/provider conflicts with the selected preset") + # All generated path references are rooted at the final directory, not staging. + deployment["storage"]["run_root"] = str((Path(output_directory) / deployment["storage"]["run_root"]).resolve()) + return training, deployment + + +def worker_defaults(): + return {"id": "worker-0", "ssh": {"host": None}, "source_dir": str(ROOT), + "runner_python": sys.executable, "docker": True, "task_roots": [str(ROOT)]} diff --git a/harborrl/options/probe.py b/harborrl/options/probe.py new file mode 100644 index 000000000..12131c5df --- /dev/null +++ b/harborrl/options/probe.py @@ -0,0 +1,39 @@ +"""Explicit, bounded local hints. Never connect to a remote machine.""" +import os +import shlex +import subprocess +import sys +from pathlib import Path + + +def command_hint(command): + try: + result = subprocess.run(command, capture_output=True, text=True, timeout=3, check=True) + return {"method": command[0], "status": "observed", "value": result.stdout.strip(), "reason": None} + except (OSError, subprocess.SubprocessError) as exc: + return {"method": command[0], "status": "unknown", "value": None, "reason": str(exc)} + + +def ssh_aliases(path): + aliases = set() + for line in Path(path).read_text(encoding="utf-8").splitlines(): + words = shlex.split(line, comments=True) + if words and words[0].lower() == "host": + aliases.update(word for word in words[1:] if not any(c in word for c in "*?!")) + return sorted(aliases) + + +def probe_local(output_directory): + try: + aliases = {"method": "ssh_config_hosts", "status": "observed", + "value": ssh_aliases(Path.home() / ".ssh/config"), "reason": "Include and Match are not evaluated"} + except (OSError, ValueError) as exc: + aliases = {"method": "ssh_config_hosts", "status": "unknown", "value": None, "reason": str(exc)} + parent = Path(output_directory).expanduser().absolute().parent + return { + "gpu": command_hint(["nvidia-smi", "--query-gpu=uuid", "--format=csv,noheader"]), + "python": command_hint([sys.executable, "-I", "--version"]), + "ssh_aliases": aliases, + "output_parent": {"method": "os.access", "status": "hint", "value": os.access(parent, os.W_OK), + "reason": "Access hint only; no write was attempted"}, + } diff --git a/harborrl/options/resources/datasets.yaml b/harborrl/options/resources/datasets.yaml new file mode 100644 index 000000000..40d8e6726 --- /dev/null +++ b/harborrl/options/resources/datasets.yaml @@ -0,0 +1,40 @@ +schema_version: 1 +datasets: + - name: hello_world + status: example + source: bundled + upstream_revision: v0.1-example + license: MIT + task_root: examples/native/tasks + expected_task_count: 1 + digest_policy: native-inspector-tree-sha256 + docker_base: debian:bookworm-slim + gpu_required: false + acceptance: offline-native-contract-only + owner: HarborRL maintainers + + - name: terminal-bench-core + status: planned + source: https://github.com/laude-institute/terminal-bench + upstream_revision: TBD + license: TBD + task_root: TBD + expected_task_count: TBD + digest_policy: native-inspector-tree-sha256 + docker_base: task-defined + gpu_required: false + acceptance: pending-public-worker-matrix + owner: HarborRL maintainers + + - name: terminal-bench-test + status: planned + source: https://github.com/laude-institute/terminal-bench + upstream_revision: dataset/terminal-bench-core/v0.1.x + license: TBD + task_root: TBD + expected_task_count: TBD + digest_policy: native-inspector-tree-sha256 + docker_base: task-defined + gpu_required: false + acceptance: pending-public-worker-matrix + owner: HarborRL maintainers diff --git a/harborrl/options/validation.py b/harborrl/options/validation.py new file mode 100644 index 000000000..53465d83e --- /dev/null +++ b/harborrl/options/validation.py @@ -0,0 +1,169 @@ +"""Offline checks composed from the authoritative configuration validators.""" +from copy import deepcopy +from pathlib import Path + +import yaml + +from harborrl.config import native +from harborrl.deployment import schema + + +def read_mapping(path): + value = yaml.safe_load(Path(path).read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise TypeError("configuration must be a mapping") + return value + + +def check(path, status, code, message): + return {"path": path, "status": status, "code": code, "message": message} + + +def report(checks, *, check_files=False, source="explicit"): + states = {item["status"] for item in checks} + status = "invalid" if "failed" in states else ( + "valid_with_deferred_checks" if "deferred" in states else "valid") + return {"validation_version": 1, "status": status, + "scope": "structure_and_local_files" if check_files else "structure", + "deployment_source": source, "checks": checks} + + +def cross_checks(training, deployment): + """Only compare settings actually consumed together by the selected mode.""" + checks = [] + + def require(condition, path, message): + if not condition: + checks.append(check(path, "failed", "inconsistent", message)) + + gpu = deployment["gpu_target"] + require(training["deployment"]["num_gpus"] <= gpu["expected_gpus"], + "deployment.gpu_target.expected_gpus", "Expected GPU count is below the training budget") + if deployment["manager"]["mode"] != "service": + return checks + manager, rollout, harbor = deployment["manager"], deployment["rollout"], training["harbor"] + for key, other in (("host", "bind"), ("port", "gateway_port"), ("advertised_url", "gateway_url")): + left, right = training["gateway"][key], manager[other] + if key == "advertised_url": + left, right = left.rstrip("/"), right.rstrip("/") + require(left == right, f"training.gateway.{key}", f"Must match deployment.manager.{other}") + require(training["output"]["root"] == deployment["storage"]["run_root"], + "training.output.root", "Must match deployment.storage.run_root") + sandbox = harbor.get("environment", {}).get("runtime") == "sandbox" + require(sandbox == (rollout["provider"] == "sandbox"), "deployment.rollout.provider", + "Sandbox provider and training environment must agree") + workers = rollout["workers"] + local = harbor.get("runner_transport", "ssh") == "local" + require(not workers if local else bool(workers), "deployment.rollout.workers", + "Local runners require no workers; SSH runners require workers") + if not local and workers: + destinations = [schema.destination(worker["ssh"]) for worker in workers] + require(harbor["workers"] == destinations, "training.harbor.workers", + "Must match deployment worker destinations in order") + require(harbor["python"] == workers[0]["runner_python"], "training.harbor.python", + "Must match the deployment's first runner_python") + return checks + + +def _local_file(checks, path, label, *, directory=False): + target = Path(path) + exists = target.is_dir() if directory else target.is_file() + checks.append(check(label, "passed" if exists else "failed", "local_path", + "Local path exists" if exists else "Required local path is missing")) + + +def _file_checks(training, deployment): + checks = [] + manager = deployment["manager"] + _local_file(checks, manager["source_dir"], "deployment.manager.source_dir", directory=True) + _local_file(checks, manager["controller_python"], "deployment.manager.controller_python") + for label, function in ( + ("training.harness.profile", lambda: native.validate_files(training)), + ("training.tasks.catalog", lambda: native.catalog(training)), + ): + try: + function() + except (ValueError, TypeError, KeyError, OSError, AttributeError) as exc: + checks.append(check(label, "failed", "local_reference", str(exc))) + else: + checks.append(check(label, "passed", "local_reference", "Local reference validated")) + gpu = deployment["gpu_target"] + if gpu["transport"] == "local": + for key in ("checkpoint", "reference"): + _local_file(checks, training["model"][key], f"training.model.{key}", directory=True) + _local_file(checks, gpu["train_python"], "deployment.gpu_target.train_python") + _local_file(checks, gpu["source_dir"], "deployment.gpu_target.source_dir", directory=True) + if gpu.get("environment_file"): + _local_file(checks, gpu["environment_file"], "deployment.gpu_target.environment_file") + else: + checks.append(check("deployment.gpu_target", "deferred", "remote_paths", + "Model, interpreter and source paths belong to the GPU target")) + if training["harbor"].get("runner_transport", "ssh") == "local": + _local_file(checks, training["harbor"]["python"], "training.harbor.python") + else: + checks.append(check("deployment.rollout.workers", "deferred", "remote_paths", + "Runner paths must be checked on their worker machines")) + environment = training["harbor"].get("environment", {}) + if environment.get("runtime") == "sandbox": + from harborrl.rollout.harbor_job.sandbox_config import ( + load_sandbox, + task_binding, + ) + try: + cfg = load_sandbox(environment["config"]) + if cfg["adapter"] == "harborrl.rollout.harbor_job.environments.opensandbox:ManagedOpenSandboxEnvironment": + if cfg["options"] or any(binding["options"] for binding in cfg["bindings"]): + raise ValueError("standard OpenSandbox does not accept platform options") + else: + checks.append(check("sandbox.adapter", "deferred", "runner_adapter", + "Platform adapter validation belongs to its trusted runner environment")) + for entry in native.catalog(training): + task_binding(cfg, entry["path"]) + except (ValueError, TypeError, KeyError, OSError, AttributeError, yaml.YAMLError) as exc: + checks.append(check("training.harbor.environment", "failed", "sandbox_config", str(exc))) + else: + checks.append(check("training.harbor.environment", "passed", "sandbox_config", + "Sandbox structure and task image bindings validated; credentials not read")) + checks.append(check("sandbox.platform", "deferred", "platform_access", + "Credentials, offline binary, capacity and network need runner preflight")) + return checks + + +def validate_mappings(training, deployment, *, config_path, check_files=False, source="explicit"): + checks = [] + normalized = {} + for name, function in ( + ("training", lambda: native.validate(deepcopy(training), Path(config_path).resolve())), + ("deployment", lambda: schema.validate(deepcopy(deployment))), + ): + try: + normalized[name] = function() + except (ValueError, TypeError, KeyError, OSError, AttributeError) as exc: + checks.append(check(name, "failed", "schema", str(exc))) + else: + checks.append(check(name, "passed", "schema", "Schema and semantic checks passed")) + if len(normalized) == 2: + checks.extend(cross_checks(normalized["training"], normalized["deployment"])) + if check_files: + checks.extend(_file_checks(normalized["training"], normalized["deployment"])) + else: + checks.append(check("references", "deferred", "files_not_checked", "Use --check-files for local references")) + checks.append(check("runtime", "deferred", "live_preflight", + "GPU memory, model loading, live digests and connectivity require doctor or an explicit live test")) + return report(checks, check_files=check_files, source=source) + + +def validate_paths(config_path, deployment_path=None, *, check_files=False): + try: + training = read_mapping(config_path) + deployment, source = schema.load_deployment(deployment_path) + if source == "embedded-local": + normalized = native.validate(deepcopy(training), Path(config_path).resolve()) + deployment = schema.embedded_local(run_root=normalized["output"]["root"], + workers=normalized["harbor"]["workers"], expected_gpus=normalized["deployment"]["num_gpus"]) + # The legacy validator omits an optional null key from its normalized output. + deployment["gpu_target"].setdefault("environment_file", None) + return validate_mappings(training, deployment, config_path=config_path, + check_files=check_files, source=source) + except (ValueError, TypeError, KeyError, OSError, AttributeError, yaml.YAMLError) as exc: + return report([check("configuration", "failed", "load", str(exc))], check_files=check_files) diff --git a/harborrl/options/wizard.py b/harborrl/options/wizard.py new file mode 100644 index 000000000..55cba4a53 --- /dev/null +++ b/harborrl/options/wizard.py @@ -0,0 +1,68 @@ +"""Build a reviewed configuration bundle, then publish it to a new directory.""" +import json +import os +import shutil +import tempfile +from pathlib import Path + +import yaml + +from harborrl.config.native import validate +from harborrl.trajectories.native import digest + +from .presets import build_configs +from .validation import validate_mappings + + +def build_config_bundle(answers, *, output_directory): + output = Path(output_directory).expanduser().resolve() + training, deployment = build_configs(answers, output) + result = validate_mappings(training, deployment, config_path=output / "config.yaml") + if result["status"] == "invalid": + raise ValueError("; ".join(f"{row['path']}: {row['message']}" for row in result["checks"] if row["status"] == "failed")) + training = validate(training, output / "config.yaml") + manifest = {"setup_version": 1, "preset": answers["preset"], + "schema_versions": {"training": 2, "deployment": 1}, + "config_digest": digest(training), "deployment_digest": digest(deployment), + "references": {"catalog": training["tasks"]["catalog"], + "profile": training["harness"]["profile"], + "sandbox_config": training["harbor"].get("environment", {}).get("config")}, + "validation": result} + return {"files": {"config.yaml": yaml.safe_dump(training, sort_keys=False), + "deployment.yaml": yaml.safe_dump(deployment, sort_keys=False), + "setup-manifest.json": json.dumps(manifest, indent=2) + "\n"}, + "validation": result} + + +def publish_bundle(bundle, output_directory): + """No overwrite; same-parent staging and an exclusive per-target lock.""" + output = Path(output_directory).expanduser().absolute() + if output.exists() or output.is_symlink(): + raise FileExistsError(f"Output directory already exists: {output}") + if not output.parent.is_dir(): + raise ValueError("Output parent directory must already exist") + files = bundle["files"] + if set(files) != {"config.yaml", "deployment.yaml", "setup-manifest.json"}: + raise ValueError("Bundle must contain exactly the three documented files") + lock = output.parent / f".{output.name}.init.lock" + descriptor = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + staging = None + try: + os.close(descriptor) + if output.exists() or output.is_symlink(): + raise FileExistsError(f"Output directory already exists: {output}") + staging = Path(tempfile.mkdtemp(prefix=f".{output.name}.init-", dir=output.parent)) + for name, content in files.items(): + with (staging / name).open("x", encoding="utf-8") as stream: + stream.write(content) + stream.flush() + os.fsync(stream.fileno()) + if output.exists() or output.is_symlink(): + raise FileExistsError(f"Output directory already exists: {output}") + staging.rename(output) + staging = None + finally: + if staging is not None: + shutil.rmtree(staging) + lock.unlink() + return output diff --git a/harborrl/platform/evidence.py b/harborrl/platform/evidence.py new file mode 100644 index 000000000..d988cf86f --- /dev/null +++ b/harborrl/platform/evidence.py @@ -0,0 +1,126 @@ +"""Run input preparation and reproducible source evidence.""" +import hashlib +import json +import subprocess +import sys +import time +from pathlib import Path +from uuid import uuid4 + +from harborrl.config import ROOT +from harborrl.trajectories.native import publish + + +def new_run_root(output_root): + name = time.strftime("%Y%m%d-%H%M%S") + "-" + uuid4().hex[:8] + return Path(output_root) / "training" / name + + +def run_environment(plan, root, catalog_path, prompt_path): + """Shared run paths for both local and controller-managed training.""" + return { + **plan["environment"], + "RUN_ID": root.name, "RUN_NAME": root.name, "RUN_DIR": str(root), + "RUNS_ROOT": str(root.parent.parent.resolve()), + "CKPT_ROOT": str(root / "checkpoints"), "WANDB_DIR": str(root / "metrics/wandb"), + "HARBORRL_NATIVE_CATALOG": str(catalog_path), + "ROLLOUT_PROMPT_DATA": str(prompt_path), + "HARBORRL_NATIVE_PROFILE": plan["config"]["harness"]["profile"], + "HARBOR_IR_ROOT": str(root / "ir"), + } + + +def _materialize(plan, root): + from harborrl.math_registry import select_math + + config = plan["config"] + entries = select_math(config).task.adapter.catalog(config) + normalized = root / "config" + normalized.mkdir(parents=True) + catalog_path = normalized / "native_catalog.json" + publish(catalog_path, entries) + prompt_path = normalized / "native_tasks.jsonl" + rows = select_math(config).task.adapter.prompt_rows(entries) + prompt_path.write_text("".join(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n" for row in rows)) + return catalog_path, prompt_path + + +def _source_audit(root): + def git(*args): + return subprocess.check_output(["git", *args], cwd=ROOT) + + files = git("ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", + "harborrl", "scripts", "examples", "pyproject.toml").decode().split("\0") + untracked = set(git("ls-files", "-z", "--others", "--exclude-standard", "--", + "harborrl", "scripts", "examples").decode().split("\0")) + (root / "source.patch").write_bytes(git("diff", "--binary", "HEAD", "--", + "harborrl", "scripts", "examples", "pyproject.toml")) + hashes = {} + for name in files: + source = ROOT / name + if name and source.is_file(): + hashes[name] = hashlib.sha256(source.read_bytes()).hexdigest() + if name in untracked: + target = root / "source-new" / name + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(source.read_bytes()) + payload = { + "commit": git("rev-parse", "HEAD").decode().strip(), + "dirty": bool(git("status", "--porcelain")), + "source_hashes": hashes, + "patch": "source.patch", + } + publish(root / "source-manifest.json", payload) + return payload + + + + +# Proxy values and provider credentials are deliberately outside this snapshot. +DEPLOYMENT_EVIDENCE_KEYS = ("SLIME_DIR", "MEGATRON_DIR", "SGLANG_IMAGE", "CUDA_VISIBLE_DEVICES") + + +def effective_config(plan, environment, child_env, source, checks): + from harborrl.backends.slime_v032.config import field_usage + config = plan["config"] + return { + "schema_version": "harborrl-effective-config-v1", + "components": { + "model_preset": config["model"]["args_file"], + "algorithm": plan["math"]["algorithm"]["id"], + "harness": plan["math"]["harness"]["id"], + "execution_backend": config["execution"]["backend"], + "training_backend_contract": config["training"]["backend_contract"], + }, + "environment": environment, + "deployment_environment": {key: child_env[key] for key in DEPLOYMENT_EVIDENCE_KEYS if key in child_env}, + "training_command": plan["training_command"], + "expanded_command_file": "effective-command.json", + "source": source, + "runtime_versions": [check for check in checks + if "version" in check or check.get("service") in + ("slime commit", "megatron commit", "sglang image")], + "field_usage": field_usage(), + "precedence": "YAML < --set in order < explicit MATH selectors; declared deployment environment only", + } + + +def record_command(root, command, *, dry_run=False): + """Record argv after shell defaults and model preset expansion.""" + payload = { + "schema_version": "harborrl-effective-command-v1", + "command": command, + "mode": "dry_run" if dry_run else "training", + } + target = Path(root) / "effective-command.json" + if dry_run: + checksum = hashlib.sha256(json.dumps(command).encode()).hexdigest() + target = Path(root) / "dry-run-commands" / f"{checksum}.json" + publish(target, payload) + + +if __name__ == "__main__": + # Internal shell boundary: RUN_DIR, mode, then the exact executable argv. + if len(sys.argv) < 4 or sys.argv[2] not in ("0", "1"): + raise SystemExit("usage: evidence RUN_DIR DRY_RUN COMMAND [ARG ...]") + record_command(sys.argv[1], sys.argv[3:], dry_run=sys.argv[2] == "1") diff --git a/harborrl/platform/native_train.py b/harborrl/platform/native_train.py index a2a356dbc..0c0eedba2 100644 --- a/harborrl/platform/native_train.py +++ b/harborrl/platform/native_train.py @@ -2,249 +2,50 @@ The dispatcher freezes a run directory and materializes the original-task lock into Slime prompt rows before handing lifecycle control to the existing Slime -launcher. It never rewrites task tests or rewards. +launcher. It never rewrites task tests or rewards. A service-mode deployment +routes through the manager-side controller instead of a local child process. """ from __future__ import annotations -import hashlib -import importlib -import importlib.metadata import json -import os -import shutil -import signal -import subprocess import sys -import time from pathlib import Path -from types import SimpleNamespace -from uuid import uuid4 -from harborrl.config import ROOT +from harborrl.deployment.training_target import local_target +from harborrl.platform.evidence import effective_config, new_run_root, run_environment from harborrl.trajectories.native import publish, read_json def _run_root(plan): - name = time.strftime("%Y%m%d-%H%M%S") + "-" + uuid4().hex[:8] - return Path(plan["config"]["output"]["root"]) / "training" / name + return new_run_root(plan["config"]["output"]["root"]) def doctor(plan): - config = plan["config"] - checks = [] - - runner = Path(config["harbor"]["python"]) - checks.append({"path": "harbor.python", "ok": runner.is_file()}) - from harborrl.rollout.harbor_job.coordinator import runner_command - if runner.is_file(): - for worker in config["harbor"]["workers"]: - try: - probe = subprocess.run( - runner_command(str(runner), ROOT, worker) + ["--probe"], - capture_output=True, text=True, timeout=30, check=False, - ) - except (subprocess.SubprocessError, OSError) as exc: - probe = SimpleNamespace(returncode=1, stdout="", stderr=str(exc)) - try: - payload = json.loads(probe.stdout) - runtime_version = tuple(payload.get("python", "").split(".")[:2]) - version_ok = payload.get("harbor_version") == config["harbor"]["version"] - runtime_ok = len(runtime_version) == 2 and all(part.isdigit() for part in runtime_version) - except (ValueError, AttributeError, TypeError): - runtime_version, version_ok, runtime_ok = (), False, False - checks.append({ - "service": "external Harbor worker", - "worker": worker, - "ok": probe.returncode == 0 and runtime_ok - and tuple(map(int, runtime_version)) >= (3, 12) and version_ok, - "detail": probe.stdout.strip() or probe.stderr.strip()[-1000:], - }) - - for key, path in (("tasks.catalog", config["tasks"]["catalog"]), - ("harness.profile", config["harness"]["profile"]), - ("model.checkpoint", config["model"]["checkpoint"]), - ("model.reference", config["model"]["reference"])): - checks.append({"path": key, "ok": Path(path).exists()}) - - model_config = Path(config["model"]["checkpoint"]) / "config.json" - if model_config.is_file(): - try: - model = json.loads(model_config.read_text()) - heads = model.get("num_attention_heads", 0) - kv_heads = model.get("num_key_value_heads", heads) - for tp in {config["deployment"]["actor_tensor_parallel_size"], - config["deployment"]["rollout_gpus_per_engine"]}: - checks.append({"model_tp": tp, "ok": bool( - heads and kv_heads and heads % tp == 0 and (kv_heads % tp == 0 or tp % kv_heads == 0))}) - except (OSError, ValueError, TypeError) as exc: - checks.append({"path": "model.config", "ok": False, "detail": str(exc)}) - - from harborrl.config.native import catalog - try: - entries = catalog(config) - for entry in entries: - from harborrl.data.harbor.native_inspector import inspect_native - report = inspect_native(entry["path"], expected_digest=entry["task_digest"]) - checks.append({"task": entry["id"], "ok": report["status"] == "NEEDS_PROBE", - "detail": report["reasons"]}) - except (ValueError, OSError, TypeError) as exc: - checks.append({"service": "native catalog", "ok": False, "detail": str(exc)}) - - for package in ("ray", "torch", "sglang", "transformers"): - try: - version = importlib.metadata.version(package) - except importlib.metadata.PackageNotFoundError: - version = None - checks.append({"dependency": package, "version": version, "ok": version is not None}) - - from harborrl.backends.slime_v032.versions import ( - MEGATRON_COMMIT, - SGLANG_IMAGE_TAG, - SLIME_COMMIT, - ) - slime_dir = os.environ.get("SLIME_DIR", "").strip() - slime_path = Path(slime_dir) if slime_dir else None - checks.append({"path": "SLIME_DIR", "ok": bool(slime_path and slime_path.is_dir()), - "detail": slime_dir or "not set"}) - if slime_path and slime_path.is_dir(): - checks.append({"path": "slime entrypoint", "ok": (slime_path / "train.py").is_file()}) - preset = slime_path / "scripts" / "models" / f"{config['model']['args_file']}.sh" - checks.append({"path": "slime model preset", "ok": preset.is_file(), "detail": str(preset)}) - try: - commit = subprocess.run(["git", "-C", str(slime_path), "rev-parse", "HEAD"], - capture_output=True, text=True, timeout=15, check=True).stdout.strip() - except (subprocess.SubprocessError, OSError) as exc: - commit = "" - checks.append({"service": "slime commit", "ok": False, "detail": str(exc)}) - if commit: - checks.append({"service": "slime commit", "ok": commit == SLIME_COMMIT, - "detail": f"{commit} != {SLIME_COMMIT}" if commit != SLIME_COMMIT else commit}) - - megatron_dir = os.environ.get("MEGATRON_DIR", "").strip() - if megatron_dir: - try: - megatron_commit = subprocess.run(["git", "-C", megatron_dir, "rev-parse", "HEAD"], - capture_output=True, text=True, timeout=15, check=True).stdout.strip() - except (subprocess.SubprocessError, OSError) as exc: - megatron_commit = "" - checks.append({"service": "megatron commit", "ok": False, "detail": str(exc)}) - if megatron_commit: - checks.append({"service": "megatron commit", "ok": megatron_commit == MEGATRON_COMMIT, - "detail": f"{megatron_commit} != {MEGATRON_COMMIT}" if megatron_commit != MEGATRON_COMMIT else megatron_commit}) - else: - checks.append({"service": "megatron commit", - "ok": True, - "detail": "MEGATRON_DIR not set; official Slime image bundles Megatron"}) - - runtime_env = os.environ.copy() - runtime_pythonpath = os.pathsep.join(filter(None, [ - str(slime_path), megatron_dir, str(ROOT), runtime_env.get("PYTHONPATH", ""), - ])) - runtime_env["PYTHONPATH"] = runtime_pythonpath - for module in ("slime.rollout.base_types", "slime.utils.types", - "slime.backends.megatron_utils.loss"): - try: - probe = subprocess.run([sys.executable, "-c", f"import {module}"], - capture_output=True, text=True, timeout=90, check=False, - env=runtime_env) - except (subprocess.SubprocessError, OSError) as exc: - probe = SimpleNamespace(returncode=1, stdout="", stderr=str(exc)) - checks.append({"dependency": module, "ok": probe.returncode == 0, - "detail": probe.stderr.strip()[-1000:]}) - - sglang_image = os.environ.get("SGLANG_IMAGE", "").strip() - if sglang_image: - checks.append({"service": "sglang image", "ok": sglang_image.endswith(SGLANG_IMAGE_TAG), - "detail": sglang_image}) - - if shutil.which("nvidia-smi"): - try: - result = subprocess.run(["nvidia-smi", "--query-gpu=index", "--format=csv,noheader"], - capture_output=True, text=True, timeout=10, check=True) - visible = os.environ.get("CUDA_VISIBLE_DEVICES") - count = (len([item for item in visible.split(",") if item.strip() and item.strip() != "-1"]) - if visible is not None else len(result.stdout.splitlines())) - checks.append({"resource": "visible GPU budget", "available": count, - "ok": count >= config["deployment"]["num_gpus"]}) - except (subprocess.SubprocessError, OSError) as exc: - checks.append({"resource": "visible GPU budget", "ok": False, "detail": str(exc)}) - else: - checks.append({"resource": "visible GPU budget", "ok": False, "detail": "nvidia-smi unavailable"}) - - if config["training"]["backend_contract"] != "slime-legacy": - from harborrl.backends.slime_v032.launcher import hook_modules - for hook_name, module_name in hook_modules().items(): - try: - importlib.import_module(module_name) - checks.append({"service": "training backend hook", "hook": hook_name, "ok": True}) - except (ImportError, AttributeError, TypeError, ValueError) as exc: - checks.append({ - "service": "training backend hook", "hook": hook_name, - "ok": False, "detail": str(exc), - }) - - checks.append({ - "service": "raw-model logprob audit", - "ok": True, - "detail": ("enabled by lock; live serving semantic probe remains a GPU gate" - if config["gateway"]["audited_raw_logprobs"] else - "disabled; generated evidence remains EVAL_ONLY and training will fail closed"), - }) - return checks + """Compatibility entry point for deployment preflight.""" + from harborrl.platform.preflight import doctor as check + return check(plan) def _materialize(plan, root): - from harborrl.config.native import catalog - - config = plan["config"] - entries = catalog(config) - normalized = root / "config" - normalized.mkdir(parents=True) - catalog_path = normalized / "native_catalog.json" - publish(catalog_path, entries) - prompt_path = normalized / "native_tasks.jsonl" - rows = [] - for entry in entries: - instruction = (Path(entry["path"]) / "instruction.md").read_text() - rows.append({"task": instruction, "metadata": {"harborrl_native": True, "native_task": entry}}) - prompt_path.write_text("".join(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n" for row in rows)) - return catalog_path, prompt_path + from harborrl.platform.evidence import _materialize as prepare + return prepare(plan, root) def _source_audit(root): - def git(*args): - return subprocess.check_output(["git", *args], cwd=ROOT) - - files = git("ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", - "harborrl", "scripts", "examples", "pyproject.toml").decode().split("\0") - untracked = set(git("ls-files", "-z", "--others", "--exclude-standard", "--", - "harborrl", "scripts", "examples").decode().split("\0")) - (root / "source.patch").write_bytes(git("diff", "--binary", "HEAD", "--", - "harborrl", "scripts", "examples", "pyproject.toml")) - hashes = {} - for name in files: - source = ROOT / name - if name and source.is_file(): - hashes[name] = hashlib.sha256(source.read_bytes()).hexdigest() - if name in untracked: - target = root / "source-new" / name - target.parent.mkdir(parents=True, exist_ok=True) - target.write_bytes(source.read_bytes()) - payload = { - "commit": git("rev-parse", "HEAD").decode().strip(), - "dirty": bool(git("status", "--porcelain")), - "source_hashes": hashes, - "patch": "source.patch", - } - publish(root / "source-manifest.json", payload) - return payload + from harborrl.platform.evidence import _source_audit as audit + return audit(root) def dispatch(plan, *, doctor_only=False): + deployment = (plan.get("deployment") or {}).get("config") if doctor_only: checks = doctor(plan) + explicit = plan.get("deployment", {}).get("source", "embedded-local") != "embedded-local" print(json.dumps({"checks": checks, - "scope": "native config, original tasks, isolated Harbor, training dependencies and GPU budget; protocol/token semantics require the deferred live probe"}, + "scope": ("deployment topology, " if explicit else "") + + "native config, original tasks, isolated Harbor, training " + "dependencies and GPU budget; protocol/token semantics " + "require the deferred live probe"}, indent=2)) return int(any(not check["ok"] for check in checks)) @@ -254,51 +55,23 @@ def dispatch(plan, *, doctor_only=False): print(json.dumps({"failed_checks": failed}, indent=2), file=sys.stderr) return 1 + if deployment is not None and deployment["manager"]["mode"] == "service": + from harborrl.deployment.controller import dispatch_service + return dispatch_service(plan, deployment, checks=checks) + root = _run_root(plan) root.mkdir(parents=True) catalog_path, prompt_path = _materialize(plan, root) source = _source_audit(root) - environment = dict(plan["environment"]) - environment.update({ - "RUN_ID": root.name, "RUN_NAME": root.name, "RUN_DIR": str(root), - "RUNS_ROOT": str(Path(plan["config"]["output"]["root"]).resolve()), - "CKPT_ROOT": str(root / "checkpoints"), "WANDB_DIR": str(root / "metrics/wandb"), - "HARBORRL_NATIVE_CATALOG": str(catalog_path), - "ROLLOUT_PROMPT_DATA": str(prompt_path), - "HARBORRL_NATIVE_PROFILE": plan["config"]["harness"]["profile"], - "HARBOR_IR_ROOT": str(root / "ir"), - }) + environment = run_environment(plan, root, catalog_path, prompt_path) launch = {"plan": plan, "run_dir": str(root), "source": source, "materialized_catalog": str(catalog_path), "prompt_data": str(prompt_path)} publish(root / "launch.json", launch) - allowed = {"PATH", "HOME", "USER", "LANG", "LC_ALL", "TMPDIR", "LD_LIBRARY_PATH", - "CUDA_VISIBLE_DEVICES", "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", - "http_proxy", "https_proxy", "no_proxy", "PYTHONPATH", - "SLIME_DIR", "MEGATRON_DIR", "SGLANG_IMAGE"} - child_env = {key: value for key, value in os.environ.items() if key in allowed} - child_env["PATH"] = os.pathsep.join( - [str(Path(sys.executable).parent), child_env.get("PATH", os.defpath)]) - child_env["PYTHONPATH"] = os.pathsep.join(filter(None, [ - child_env.get("SLIME_DIR", ""), child_env.get("MEGATRON_DIR", ""), - str(ROOT), child_env.get("PYTHONPATH", ""), - ])) - child_env.update(environment) - child = subprocess.Popen(plan["training_command"], cwd=ROOT, env=child_env, start_new_session=True) - - def forward_signal(signum, frame): - if child.poll() is None: - try: - child.send_signal(signum) - except ProcessLookupError: - pass - - previous = {sig: signal.signal(sig, forward_signal) for sig in (signal.SIGINT, signal.SIGTERM)} - try: - return child.wait() - finally: - for sig, handler in previous.items(): - signal.signal(sig, handler) + target = local_target(plan, environment=environment) + publish(root / "effective-config.json", effective_config(plan, environment, target.env, source, checks)) + target.launch() + return target.wait() if __name__ == "__main__": diff --git a/harborrl/platform/policy_pool.py b/harborrl/platform/policy_pool.py index 697ebe2d4..945e9d09c 100644 --- a/harborrl/platform/policy_pool.py +++ b/harborrl/platform/policy_pool.py @@ -20,6 +20,8 @@ def validate_pool(states, expected=None): def publish_pool(states, expected=None): version = validate_pool(states, expected) root = Path(os.environ['RUN_DIR']) + from harborrl.consistency.sglang import admit_pool + admit_pool(states, root) payload = {'weight_version': version, 'engines': states, 'timestamp': time.time()} temp = root / 'policy-pool.tmp' temp.write_text(json.dumps(payload) + '\n') diff --git a/harborrl/platform/preflight.py b/harborrl/platform/preflight.py new file mode 100644 index 000000000..3a35fe88f --- /dev/null +++ b/harborrl/platform/preflight.py @@ -0,0 +1,220 @@ +"""Live deployment checks for the native pipeline; no training dispatch. + +``doctor`` composes two layers: the deployment topology graph (manager, GPU +target, rollout workers, control-loop addresses) and the machine-local +environment checks. The local layer only runs where it is meaningful: when +the GPU target is remote, dependency probes belong to the GPU job startup, +not to this machine. +""" +import importlib +import importlib.metadata +import json +import os +import shutil +import subprocess +import sys +from pathlib import Path +from types import SimpleNamespace + +from harborrl.config import ROOT + + +def _deployment(plan): + return (plan.get("deployment") or {}).get("config") + + +def doctor(plan): + config = plan["config"] + checks = [] + deployment = _deployment(plan) + # Embedded-local is a compatibility record, not an opt-in: only an + # explicit deployment file activates the topology layers. + explicit = deployment is not None and plan.get("deployment", {}).get( + "source", "embedded-local") != "embedded-local" + local_gpu = deployment is None or deployment["gpu_target"]["transport"] == "local" + legacy_workers = (not explicit) or deployment["rollout"]["provider"] in ("embedded", "sandbox") + + if explicit: + from harborrl.deployment.preflight import preflight as deployment_preflight + checks.extend(deployment_preflight(plan, deployment, live=True)) + if not local_gpu: + checks.append({ + "role": "gpu_target", "machine": deployment["gpu_target"]["id"], + "check": "gpu_target.training_dependencies", "ok": True, + "detail": "Slime/Megatron/SGlang versions are enforced fail-closed " + "by slime_train.sh at GPU job startup on the GPU machine", + }) + + runner = Path(config["harbor"]["python"]) + checks.append({"path": "harbor.python", "ok": runner.is_file()}) + from harborrl.rollout.harbor_job.coordinator import runner_command + if legacy_workers and runner.is_file(): + workers = ([None] if config['harbor'].get('runner_transport') == 'local' + else config['harbor']['workers']) + for worker in workers: + try: + probe = subprocess.run( + runner_command(str(runner), ROOT, worker) + ["--probe"], + capture_output=True, text=True, timeout=30, check=False, + ) + except (subprocess.SubprocessError, OSError) as exc: + probe = SimpleNamespace(returncode=1, stdout="", stderr=str(exc)) + try: + payload = json.loads(probe.stdout) + runtime_version = tuple(payload.get("python", "").split(".")[:2]) + version_ok = payload.get("harbor_version") == config["harbor"]["version"] + runtime_ok = len(runtime_version) == 2 and all(part.isdigit() for part in runtime_version) + except (ValueError, AttributeError, TypeError): + runtime_version, version_ok, runtime_ok = (), False, False + checks.append({ + "service": "local Harbor runner" if worker is None else "external Harbor worker", + "worker": worker, + "ok": probe.returncode == 0 and runtime_ok + and tuple(map(int, runtime_version)) >= (3, 12) and version_ok, + "detail": probe.stdout.strip() or probe.stderr.strip()[-1000:], + }) + if 'HARBORRL_NATIVE_SANDBOX' in plan['environment']: + for worker in workers: + try: + probe = subprocess.run( + runner_command(str(runner), ROOT, worker, + module='harborrl.rollout.harbor_job.sandbox_probe') + ['--stdin'], + input=plan['environment']['HARBORRL_NATIVE_SANDBOX'], cwd=ROOT, + capture_output=True, text=True, timeout=60, check=False) + checks.append({'service': 'sandbox offline runtime', 'worker': worker, + 'ok': probe.returncode == 0, 'detail': probe.stdout.strip()}) + except (subprocess.SubprocessError, OSError) as exc: + checks.append({'service': 'sandbox offline runtime', 'worker': worker, + 'ok': False, 'detail': type(exc).__name__}) + + for key, path in (("tasks.catalog", config["tasks"]["catalog"]), + ("harness.profile", config["harness"]["profile"]), + ("model.checkpoint", config["model"]["checkpoint"]), + ("model.reference", config["model"]["reference"])): + checks.append({"path": key, "ok": Path(path).exists()}) + + model_config = Path(config["model"]["checkpoint"]) / "config.json" + if model_config.is_file(): + try: + model = json.loads(model_config.read_text()) + heads = model.get("num_attention_heads", 0) + kv_heads = model.get("num_key_value_heads", heads) + for tp in {config["deployment"]["actor_tensor_parallel_size"], + config["deployment"]["rollout_gpus_per_engine"]}: + checks.append({"model_tp": tp, "ok": bool( + heads and kv_heads and heads % tp == 0 and (kv_heads % tp == 0 or tp % kv_heads == 0))}) + except (OSError, ValueError, TypeError) as exc: + checks.append({"path": "model.config", "ok": False, "detail": str(exc)}) + + from harborrl.tasks.catalog import catalog + try: + entries = catalog(config) + for entry in entries: + # catalog() already checks the complete task tree against its lock. + checks.append({"task": entry["id"], "ok": True, "detail": []}) + except (ValueError, OSError, TypeError) as exc: + checks.append({"service": "native catalog", "ok": False, "detail": str(exc)}) + + if local_gpu: + for package in ("ray", "torch", "sglang", "transformers"): + try: + version = importlib.metadata.version(package) + except importlib.metadata.PackageNotFoundError: + version = None + checks.append({"dependency": package, "version": version, "ok": version is not None}) + + from harborrl.backends.slime_v032.versions import ( + MEGATRON_COMMIT, + SGLANG_IMAGE_TAG, + SLIME_COMMIT, + ) + if local_gpu: + slime_dir = os.environ.get("SLIME_DIR", "").strip() + slime_path = Path(slime_dir) if slime_dir else None + checks.append({"path": "SLIME_DIR", "ok": bool(slime_path and slime_path.is_dir()), + "detail": slime_dir or "not set"}) + if slime_path and slime_path.is_dir(): + checks.append({"path": "slime entrypoint", "ok": (slime_path / "train.py").is_file()}) + preset = slime_path / "scripts" / "models" / f"{config['model']['args_file']}.sh" + checks.append({"path": "slime model preset", "ok": preset.is_file(), "detail": str(preset)}) + try: + commit = subprocess.run(["git", "-C", str(slime_path), "rev-parse", "HEAD"], + capture_output=True, text=True, timeout=15, check=True).stdout.strip() + except (subprocess.SubprocessError, OSError) as exc: + commit = "" + checks.append({"service": "slime commit", "ok": False, "detail": str(exc)}) + if commit: + checks.append({"service": "slime commit", "ok": commit == SLIME_COMMIT, + "detail": f"{commit} != {SLIME_COMMIT}" if commit != SLIME_COMMIT else commit}) + + megatron_dir = os.environ.get("MEGATRON_DIR", "").strip() + if megatron_dir: + try: + megatron_commit = subprocess.run(["git", "-C", megatron_dir, "rev-parse", "HEAD"], + capture_output=True, text=True, timeout=15, check=True).stdout.strip() + except (subprocess.SubprocessError, OSError) as exc: + megatron_commit = "" + checks.append({"service": "megatron commit", "ok": False, "detail": str(exc)}) + if megatron_commit: + checks.append({"service": "megatron commit", "ok": megatron_commit == MEGATRON_COMMIT, + "detail": f"{megatron_commit} != {MEGATRON_COMMIT}" if megatron_commit != MEGATRON_COMMIT else megatron_commit}) + else: + checks.append({"service": "megatron commit", + "ok": True, + "detail": "MEGATRON_DIR not set; official Slime image bundles Megatron"}) + + runtime_env = os.environ.copy() + runtime_pythonpath = os.pathsep.join(filter(None, [ + str(slime_path), megatron_dir, str(ROOT), runtime_env.get("PYTHONPATH", ""), + ])) + runtime_env["PYTHONPATH"] = runtime_pythonpath + for module in ("slime.rollout.base_types", "slime.utils.types", + "slime.backends.megatron_utils.loss"): + try: + probe = subprocess.run([sys.executable, "-c", f"import {module}"], + capture_output=True, text=True, timeout=90, check=False, + env=runtime_env) + except (subprocess.SubprocessError, OSError) as exc: + probe = SimpleNamespace(returncode=1, stdout="", stderr=str(exc)) + checks.append({"dependency": module, "ok": probe.returncode == 0, + "detail": probe.stderr.strip()[-1000:]}) + + sglang_image = os.environ.get("SGLANG_IMAGE", "").strip() + if sglang_image: + checks.append({"service": "sglang image", "ok": sglang_image.endswith(SGLANG_IMAGE_TAG), + "detail": sglang_image}) + + if shutil.which("nvidia-smi"): + try: + result = subprocess.run(["nvidia-smi", "--query-gpu=index", "--format=csv,noheader"], + capture_output=True, text=True, timeout=10, check=True) + visible = os.environ.get("CUDA_VISIBLE_DEVICES") + count = (len([item for item in visible.split(",") if item.strip() and item.strip() != "-1"]) + if visible is not None else len(result.stdout.splitlines())) + checks.append({"resource": "visible GPU budget", "available": count, + "ok": count >= config["deployment"]["num_gpus"]}) + except (subprocess.SubprocessError, OSError) as exc: + checks.append({"resource": "visible GPU budget", "ok": False, "detail": str(exc)}) + else: + checks.append({"resource": "visible GPU budget", "ok": False, "detail": "nvidia-smi unavailable"}) + + if config["training"]["backend_contract"] != "slime-legacy": + from harborrl.backends.slime_v032.launcher import hook_modules + for hook_name, module_name in hook_modules().items(): + try: + importlib.import_module(module_name) + checks.append({"service": "training backend hook", "hook": hook_name, "ok": True}) + except (ImportError, AttributeError, TypeError, ValueError) as exc: + checks.append({ + "service": "training backend hook", "hook": hook_name, + "ok": False, "detail": str(exc), + }) + + checks.append({ + "service": "raw-model logprob audit", + "ok": True, + "detail": ("enabled by lock; live serving semantic probe remains a GPU gate" + if config["gateway"]["audited_raw_logprobs"] else + "disabled; generated evidence remains EVAL_ONLY and training will fail closed"), + }) + return checks diff --git a/harborrl/platform/slime_train.sh b/harborrl/platform/slime_train.sh index 50377dbab..8d7972874 100644 --- a/harborrl/platform/slime_train.sh +++ b/harborrl/platform/slime_train.sh @@ -171,12 +171,19 @@ TRAIN_ARGS=( export CUDA_DEVICE_MAX_CONNECTIONS=1 TRAIN_PYTHON="${TRAIN_PYTHON:-python3}" export PYTHONPATH="${REPO_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" +ENTRY=("${SLIME_DIR}/train.py") +if [[ "${HARBORRL_CONSISTENCY_MODE:-off}" != "off" ]]; then + ENTRY=(-m harborrl.consistency.train) +fi + +"${TRAIN_PYTHON}" -m harborrl.platform.evidence "${RUN_DIR}" "${DRY_RUN:-0}" \ + "${TRAIN_PYTHON}" -u "${ENTRY[@]}" "${TRAIN_ARGS[@]}" if [[ "${DRY_RUN:-0}" == "1" ]]; then printf '[dry-run] ' - printf '%q ' "${TRAIN_PYTHON}" -u "${SLIME_DIR}/train.py" "${TRAIN_ARGS[@]}" + printf '%q ' "${TRAIN_PYTHON}" -u "${ENTRY[@]}" "${TRAIN_ARGS[@]}" printf '\n' exit 0 fi -exec "${TRAIN_PYTHON}" -u "${SLIME_DIR}/train.py" "${TRAIN_ARGS[@]}" +exec "${TRAIN_PYTHON}" -u "${ENTRY[@]}" "${TRAIN_ARGS[@]}" diff --git a/harborrl/rollout/exporters/native.py b/harborrl/rollout/exporters/native.py index 55355c7aa..05fb63be8 100644 --- a/harborrl/rollout/exporters/native.py +++ b/harborrl/rollout/exporters/native.py @@ -1,47 +1,36 @@ -"""Trajectory-aware GRPO export, independent of Slime/Torch imports. +"""Compatibility record view of the canonical trajectory-weighted GRPO batch. -These records must enter a backend that honors token_weights and advantages. -They must NOT be passed through the interactive reward/whitening builder. +No independent advantage calculation or secondary whitening occurs here. """ -import math - -from harborrl.trajectories.native import Identity, finite, require_ready +from harborrl.export.native import export_training_batch +from harborrl.export.validate import token_weight +from harborrl.trajectories.native import Identity def export_group(trajectories, group_size, *, epsilon=1e-6, allow_synthetic=False): if type(group_size) is not int or group_size < 2 or len(trajectories) != group_size: raise ValueError("a complete group of at least two trajectories is required") - if not finite(epsilon) or epsilon <= 0: - raise ValueError("epsilon must be positive and finite") - for ir in trajectories: - require_ready(ir, allow_synthetic=allow_synthetic) - ids = [Identity(**ir["identity"]) for ir in trajectories] - if len({i.group_key for i in ids}) != 1: - raise ValueError("mixed group, policy, task or profiles") - for key in ("slot_id", "attempt_id", "trajectory_id"): - if len({getattr(i, key) for i in ids}) != group_size: - raise ValueError(f"duplicate {key}") - rewards = [ir["evaluation"]["training_reward"] for ir in trajectories] - mean = math.fsum(rewards) / group_size - std = math.sqrt(math.fsum((r - mean) ** 2 for r in rewards) / group_size) - if not finite(mean) or not finite(std): - raise ValueError("nonfinite group statistics") - advantages = [0.0 if std == 0 else (r - mean) / (std + epsilon) for r in rewards] + batch = export_training_batch( + [trajectories], group_size=group_size, epsilon=epsilon, + allow_synthetic=allow_synthetic, + ) + group = batch.groups[0] records = [] - for ir, ident, advantage in zip(trajectories, ids, advantages): - count = sum(len(t["output_ids"]) for t in ir["turns"]) - for index, turn in enumerate(ir["turns"]): - n = len(turn["output_ids"]) - records.append({"identity": ident.to_dict(), "turn_index": index, - "tokens": turn["input_ids"] + turn["output_ids"], - "prompt_length": len(turn["input_ids"]), "response_length": n, - "loss_mask": [0] * len(turn["input_ids"]) + [1] * n, - "old_logprobs": turn["logprobs"], "advantages": [advantage] * n, - "token_weights": [1.0 / (group_size * count)] * n, - "training_reward": ir["evaluation"]["training_reward"], - "padding": False}) - return {"export_version": "native-grpo-v2", "group_key": ids[0].group_key, - "trajectory_count": group_size, "mean": mean, "std": std, - "advantages": advantages, "records": records, + for ir, unit in zip(trajectories, group.trajectories): + ident = Identity(**ir["identity"]) + weight = token_weight(group_size, unit.token_count) + for span in unit.turns: + n = span.response_length + records.append({ + "identity": ident.to_dict(), "turn_index": span.turn_index, + "tokens": list(span.tokens), "prompt_length": span.prompt_length, + "response_length": n, "loss_mask": [int(flag) for flag in span.loss_mask], + "old_logprobs": list(span.old_logprobs), "advantages": [unit.advantage] * n, + "token_weights": [weight] * n, "training_reward": ir["evaluation"]["training_reward"], + "padding": False, + }) + return {"export_version": "native-grpo-v2", "group_key": group.group_key, + "trajectory_count": group_size, "mean": group.reward_mean, "std": group.reward_std, + "advantages": [unit.advantage for unit in group.trajectories], "records": records, "synthetic": allow_synthetic, "reduction": "sum_token_weighted_loss; no secondary whitening"} diff --git a/harborrl/rollout/harbor_job/__main__.py b/harborrl/rollout/harbor_job/__main__.py index 01828fd19..da10c1bb6 100644 --- a/harborrl/rollout/harbor_job/__main__.py +++ b/harborrl/rollout/harbor_job/__main__.py @@ -13,8 +13,10 @@ def main(argv=None): smoke.add_argument("--output", required=True) args = parser.parse_args(argv) if args.command == "inspect": - from harborrl.data.harbor.native_inspector import inspect_native - result = inspect_native(args.task, expected_digest=args.expected_digest) + from harborrl.math_registry import NATIVE_TASK_PROVIDER + result = NATIVE_TASK_PROVIDER.adapter.inspect( + args.task, expected_digest=args.expected_digest + ) print(json.dumps(result, indent=2)) return int(result["status"] in ("INVALID", "UNSUPPORTED")) from .offline import smoke as run_smoke diff --git a/harborrl/rollout/harbor_job/bindings.py b/harborrl/rollout/harbor_job/bindings.py index 4cd439689..e0e553e85 100644 --- a/harborrl/rollout/harbor_job/bindings.py +++ b/harborrl/rollout/harbor_job/bindings.py @@ -5,8 +5,9 @@ def claude_config(profile, gateway_url, credential, max_output_tokens=8192): required = {"cli_version", "model", "max_turns", "agent_timeout_sec", "setup_timeout_sec", "verifier_timeout_sec"} - if set(profile) != required: - raise ValueError(f"Claude profile requires exactly {sorted(required)}") + allowed = required | {"disallowed_tools"} + if not required.issubset(profile) or set(profile) - allowed: + raise ValueError(f"Claude profile requires {sorted(required)} and optionally disallowed_tools") if not isinstance(profile["cli_version"], str) or not re.fullmatch(r"\d+\.\d+\.\d+(?:-[A-Za-z0-9.]+)?", profile["cli_version"]): raise ValueError("exact Claude CLI version required; latest is forbidden") if not isinstance(profile["model"], str) or not re.fullmatch(r"[A-Za-z0-9_.:/-]+", profile["model"]): @@ -14,6 +15,10 @@ def claude_config(profile, gateway_url, credential, max_output_tokens=8192): for key in required - {"model", "cli_version"}: if type(profile[key]) is not int or profile[key] <= 0: raise ValueError(f"positive integer required: {key}") + disallowed_tools = profile.get("disallowed_tools", "Agent,Task") + if not isinstance(disallowed_tools, str) or not re.fullmatch( + r"[A-Za-z][A-Za-z0-9_]*(?:,[A-Za-z][A-Za-z0-9_]*)*", disallowed_tools): + raise ValueError("disallowed tools must be a comma-separated identifier list") url = urlsplit(gateway_url) if url.scheme not in ("http", "https") or not url.hostname or url.username or url.password or url.query or url.fragment or url.path not in ("", "/"): raise ValueError("Anthropic base URL must be a plain origin (no /v1 suffix)") @@ -27,9 +32,10 @@ def claude_config(profile, gateway_url, credential, max_output_tokens=8192): "override_timeout_sec": profile["agent_timeout_sec"], "override_setup_timeout_sec": profile["setup_timeout_sec"], "kwargs": {"version": profile["cli_version"], "max_turns": profile["max_turns"], - "max_thinking_tokens": 0, "disallowed_tools": "Agent,Task", + "max_thinking_tokens": 0, "disallowed_tools": disallowed_tools, "permission_mode": "bypassPermissions"}, "env": {**client_controls(), "ANTHROPIC_API_KEY": credential, + "ANTHROPIC_MODEL": profile["model"], "ANTHROPIC_BASE_URL": gateway_url.rstrip("/"), "NO_PROXY": url.hostname, "no_proxy": url.hostname, "CLAUDE_CODE_MAX_OUTPUT_TOKENS": str(max_output_tokens)}} diff --git a/harborrl/rollout/harbor_job/coordinator.py b/harborrl/rollout/harbor_job/coordinator.py index 4afa6a27d..17047cd40 100644 --- a/harborrl/rollout/harbor_job/coordinator.py +++ b/harborrl/rollout/harbor_job/coordinator.py @@ -12,10 +12,9 @@ from dataclasses import replace from pathlib import Path +from harborrl.math_registry import CLAUDE_CODE_HARNESS from harborrl.trajectories.native import assemble, publish -from .audit import audit_session -from .bindings import sanitized_runner_env from .collector import collect @@ -66,8 +65,7 @@ def export(self): return export_group([self.completed[slot] for slot in self.identities], len(self.identities)) -def runner_command(runner_python, project, runner_host=None): - module = "harborrl.rollout.harbor_job.runner" +def runner_command(runner_python, project, runner_host=None, *, module="harborrl.rollout.harbor_job.runner"): if runner_host is None: return [runner_python, "-m", module] remote = f"cd {shlex.quote(str(project))} && exec {shlex.quote(runner_python)} -m {module}" @@ -77,13 +75,13 @@ def runner_command(runner_python, project, runner_host=None): async def run_attempt(identity, *, task_path, profile, gateway_url, runner_python, root, registry, reward_profile, timeout=1800, cleanup_timeout=120, - max_output_tokens=8192, runner_host=None): + max_output_tokens=8192, runner_host=None, sandbox=None): """One real trial. CPU-only tests use a separate fake worker, never this path.""" root = Path(root).resolve() root.mkdir(parents=True, exist_ok=False) credential = secrets.token_urlsafe(32) project = str(Path(__file__).resolve().parents[3]) - env = sanitized_runner_env(os.environ) + env = CLAUDE_CODE_HARNESS.adapter.runner_environment(os.environ) env["PYTHONPATH"] = project stderr = (root / "runner.stderr").open("wb") try: @@ -100,6 +98,12 @@ async def run_attempt(identity, *, task_path, profile, gateway_url, runner_pytho req = {"identity": identity.to_dict(), "task_path": str(Path(task_path).resolve()), "profile": profile, "gateway_url": gateway_url, "credential": credential, "output": str(root / "runner"), "max_output_tokens": max_output_tokens} + if sandbox is not None: + from harborrl.trajectories.native import digest + req.update(sandbox=sandbox, runtime_digest=digest(sandbox)) + timeout = (sandbox['create_timeout_s'] + sandbox['cleanup_timeout_s'] * 2 + + sum(profile[k] for k in ('setup_timeout_sec', 'agent_timeout_sec', 'verifier_timeout_sec'))) + cleanup_timeout = sandbox['cleanup_timeout_s'] + 15 process.stdin.write(json.dumps(req).encode() + b"\n") await process.stdin.drain() @@ -133,16 +137,26 @@ async def lifecycle(): await asyncio.wait_for(process.wait(), cleanup_timeout) if process.returncode != 0 or terminal["type"] != "terminal": raise RuntimeError("native trial did not finish normally") + if sandbox is not None: + from harborrl.trajectories.native import read_json + lifecycle_path = root / 'runner' / 'sandbox-lifecycle.json' + lifecycle = read_json(lifecycle_path) if lifecycle_path.is_file() else {} + if lifecycle.get('state') != 'deleted' or lifecycle.get('runtime_digest') != req['runtime_digest']: + publish(root / 'cleanup-required.json', {'identity': identity.to_dict(), 'reason': 'Sandbox deletion unconfirmed'}) + raise RuntimeError('sandbox deletion unconfirmed') trial_dir = Path(terminal["trial_dir"]).resolve() result_path = Path(terminal["result_path"]).resolve() if not trial_dir.is_relative_to(root / "runner" / "harbor") or result_path != trial_dir / "result.json": raise ValueError("Runner result path is outside this attempt") - sessions = [p for p in (trial_dir / "agent" / "sessions" / "projects").rglob("*.jsonl")] + sessions = list((trial_dir / "agent" / "sessions" / "projects").rglob("*.jsonl")) if len(sessions) != 1: raise ValueError("expected exactly one native Claude session; auxiliary sessions require review") - audit_session(sessions[0], registry, identity.attempt_id) + registry.wait_delivered(identity.attempt_id, 30.0) + audit = CLAUDE_CODE_HARNESS.adapter.audit_session(sessions[0], registry, identity.attempt_id) + receipt = collect(identity, trial_id, result_path, trial_dir / "verifier", reward_profile) + CLAUDE_CODE_HARNESS.adapter.audit_empty_final( + trial_dir / "agent" / "claude-code.txt", registry, identity.attempt_id, audit) turns, seal = registry.seal(identity.attempt_id) - receipt = collect(identity, trial_id, terminal["result_path"], trial_dir / "verifier", reward_profile) publish(root / "evaluation.json", receipt) ir = assemble(identity, trial_id, turns, seal, receipt) publish(root / "trajectory.v2.json", ir) @@ -152,6 +166,8 @@ async def lifecycle(): publish(root / "failure.json", {"identity": identity.to_dict(), "error_type": type(exc).__name__}) raise finally: + if sandbox is not None and (root / 'runner' / 'cleanup-required.json').exists(): + publish(root / 'cleanup-required.json', {'identity': identity.to_dict(), 'reason': 'Inspect runner/cleanup-required.json'}) if registered: registry.close(identity.attempt_id) if process.returncode is None: diff --git a/harborrl/rollout/harbor_job/environments/__init__.py b/harborrl/rollout/harbor_job/environments/__init__.py new file mode 100644 index 000000000..d73e6b751 --- /dev/null +++ b/harborrl/rollout/harbor_job/environments/__init__.py @@ -0,0 +1 @@ +"""Optional Harbor environment adapters loaded only by the isolated runner.""" diff --git a/harborrl/rollout/harbor_job/environments/opensandbox.py b/harborrl/rollout/harbor_job/environments/opensandbox.py new file mode 100644 index 000000000..829f9dadc --- /dev/null +++ b/harborrl/rollout/harbor_job/environments/opensandbox.py @@ -0,0 +1,189 @@ +"""Audited lifecycle around Harbor's OpenSandbox environment.""" +import asyncio +import fcntl +import json +import os +import tempfile +import time +from contextlib import asynccontextmanager +from datetime import timedelta +from pathlib import Path + +import httpx +from harbor.environments.opensandbox import OpenSandboxEnvironment + +from harborrl.rollout.harbor_job.sandbox_config import read_credential +from harborrl.trajectories.native import digest, publish + + +@asynccontextmanager +async def startup_slot(directory, count): + """Process-shared creation slots; kernel releases locks on runner death.""" + directory = Path(directory) + directory.mkdir(parents=True, exist_ok=True) + held = None + try: + while held is None: + for index in range(count): + handle = (directory / f'{index}.lock').open('a') + try: + fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) + held = handle + break + except BlockingIOError: + handle.close() + if held is None: + await asyncio.sleep(0.2) + yield + finally: + if held is not None: + held.close() + + +class ManagedOpenSandboxEnvironment(OpenSandboxEnvironment): + @classmethod + def validate_config(cls, cfg): + if cfg['options'] or any(b['options'] for b in cfg['bindings']): + raise ValueError('standard OpenSandbox adapter does not accept platform options') + + @classmethod + def agent_gateway(cls, cfg, gateway_url): + return gateway_url + + @classmethod + async def network_check(cls, cfg): + return {'status': 'not_probed', 'reason': 'Use the explicit sandbox smoke test for allocation checks'} + + async def validate_binding(self): + pass + + def extensions(self): + return {} + + def api_params(self): + return {} + + def startup_transport(self, allocated): + from harborrl.rollout.harbor_job.sandbox_transport import ( + SandboxStartupTransport, + ) + return SandboxStartupTransport(self.cfg['domain'].rstrip('/') + '/v1', allocated) + + def __init__(self, *args, sandbox_config, binding, evidence_dir, startup_dir, gateway_url=None, **kwargs): + self.validate_config(sandbox_config) + self.cfg, self.binding = sandbox_config, binding + self.evidence = Path(evidence_dir) + self.startup_dir = startup_dir + self.gateway_url = gateway_url + self._startup_transport = None + self.record = {'runtime_digest': digest(sandbox_config), + 'image': binding['image'], 'sandbox_id': None, 'state': 'pending', 'events': []} + super().__init__(*args, domain=self.cfg['domain'], api_key=read_credential(self.cfg), + protocol=self.cfg['protocol'], use_server_proxy=True, + ready_timeout_sec=self.cfg['create_timeout_s'], + sandbox_timeout_sec=self.cfg['lifecycle_minutes'] * 60, + request_timeout_sec=self.cfg['request_timeout_s'], **kwargs) + + def record_state(self, state): + self.record['state'] = state + self.record['events'].append({'state': state, 'time': time.time()}) + publish(self.evidence / 'sandbox-events' / f'{len(self.record["events"]):04d}.json', self.record) + with tempfile.NamedTemporaryFile(mode='w', dir=self.evidence, delete=False) as stream: + json.dump(self.record, stream) + stream.flush() + os.fsync(stream.fileno()) + os.replace(stream.name, self.evidence / 'sandbox-lifecycle.json') + + def cleanup_required(self, reason): + publish(self.evidence / 'cleanup-required.json', { + 'sandbox_id': self.record['sandbox_id'], 'image': self.binding['image'], + 'reason': reason, 'runtime_digest': self.record['runtime_digest']}) + + async def _create_sandbox(self, sdk): + await self.validate_binding() + async with startup_slot(self.startup_dir, self.cfg['startup_concurrency']): + self.record_state('creating') + def allocated(sandbox_id): + self.record['sandbox_id'] = sandbox_id + self.record_state('allocated') + + self._startup_transport = self.startup_transport(allocated) + connection = self._build_connection_config(sdk).model_copy(update={'transport': self._startup_transport}) + try: + sandbox = await asyncio.wait_for(sdk['Sandbox'].create( + self.binding['image'], connection_config=connection, + timeout=timedelta(minutes=self.cfg['lifecycle_minutes']), + ready_timeout=timedelta(seconds=self.cfg['create_timeout_s']), + extensions=self.extensions(), + resource=self._build_resource(), env=dict(self._persistent_env), + network_policy=self._build_network_policy(sdk), skip_health_check=True), + self.cfg['create_timeout_s']) + except BaseException: # noqa: BLE001 - persist unknown allocation on cancellation too + if self.record['sandbox_id']: + await self._cleanup_allocated() + else: + self.record_state('creation_unknown') + self.cleanup_required('Creation outcome unknown; reconcile platform inventory before retry') + await self._startup_transport.aclose() + raise RuntimeError('Sandbox creation outcome unknown') from None + self._sandbox = sandbox + self.record['sandbox_id'] = sandbox.id + self.record_state('created') + try: + await asyncio.wait_for(self._wait_until_ready(sandbox), self.cfg['create_timeout_s']) + self.record_state('ready') + return sandbox + except BaseException: # noqa: BLE001 - cleanup before redacting SDK errors + await self.stop(delete=True) + raise RuntimeError('Sandbox readiness failed') from None + + async def _cleanup_allocated(self): + """The SDK may already have killed an instance whose endpoints failed.""" + self.record_state('deleting') + url = self.cfg['domain'].rstrip('/') + '/v1/sandboxes/' + self.record['sandbox_id'] + try: + async with asyncio.timeout(self.cfg['cleanup_timeout_s']): + async with httpx.AsyncClient(timeout=30, headers={'OPEN-SANDBOX-API-KEY': self._api_key}, + params=self.api_params()) as client: + status = await client.get(url) + if status.status_code != 404: + (await client.delete(url)).raise_for_status() + status = await client.get(url) + if status.status_code != 404: + raise RuntimeError('allocation deletion unconfirmed') + self.record_state('deleted') + except BaseException: # noqa: BLE001 - retain debt if cleanup is interrupted + self.record_state('delete_failed') + self.cleanup_required('Allocated Sandbox deletion was not confirmed') + + async def start(self, force_build): + try: + await super().start(force_build) + except BaseException: + if self._sandbox is not None: + await self.stop(delete=True) + raise + + async def stop(self, delete=True): + if self._sandbox is None: + if self._startup_transport is not None: + await self._startup_transport.aclose() + return + sandbox = self._sandbox + self.record_state('deleting') + try: + await asyncio.wait_for(sandbox.kill(), self.cfg['cleanup_timeout_s']) + self.record_state('deleted') + (self.evidence / 'cleanup-required.json').unlink(missing_ok=True) + self._sandbox = None + except BaseException: # noqa: BLE001 - preserve cleanup debt on cancellation + self.record_state('delete_failed') + self.cleanup_required('Sandbox kill was not acknowledged') + raise RuntimeError('Sandbox deletion failed') from None + finally: + try: + await asyncio.wait_for(sandbox.close(), 10) + except Exception: # noqa: BLE001 - close errors must not expose SAT headers + self.logger.warning('Sandbox SDK connection close failed') + if self._startup_transport is not None: + await self._startup_transport.aclose() diff --git a/harborrl/rollout/harbor_job/offline.py b/harborrl/rollout/harbor_job/offline.py index 7dfa3220e..8f5dea362 100644 --- a/harborrl/rollout/harbor_job/offline.py +++ b/harborrl/rollout/harbor_job/offline.py @@ -7,6 +7,7 @@ from harborrl.gateway.server import Gateway, make_server from harborrl.gateway.trace import TraceRegistry +from harborrl.math_registry import CLAUDE_CODE_HARNESS from harborrl.rollout.exporters.native import export_group from harborrl.trajectories.native import ( Identity, @@ -17,7 +18,6 @@ require_ready, ) -from .audit import audit_session from .collector import collect @@ -72,7 +72,9 @@ def smoke(output): events.append({"sessionId": trial, "type": "assistant", "message": message}) session.write_text("".join(json.dumps(e) + "\n" for e in events)) registry.close(identity.attempt_id) - audit_session(session, registry, identity.attempt_id) + CLAUDE_CODE_HARNESS.adapter.audit_session( + session, registry, identity.attempt_id + ) turns, seal = registry.seal(identity.attempt_id) publish(directory / "result.json", {"id": trial, "finished_at": "synthetic", "verifier_environment_mode": "shared", "verifier_result": {"rewards": {"reward": slot}}}) diff --git a/harborrl/rollout/harbor_job/runner.py b/harborrl/rollout/harbor_job/runner.py index a5b1c9389..7aa23fe4a 100644 --- a/harborrl/rollout/harbor_job/runner.py +++ b/harborrl/rollout/harbor_job/runner.py @@ -14,11 +14,9 @@ import sys from pathlib import Path -from harborrl.data.harbor.native_inspector import inspect_native +from harborrl.math_registry import CLAUDE_CODE_HARNESS, NATIVE_TASK_PROVIDER from harborrl.trajectories.native import Identity, digest, publish -from .bindings import claude_config - def probe(): if sys.version_info < (3, 12): @@ -43,7 +41,9 @@ async def execute(request, emit, read_command): identity = Identity(**request["identity"]) if digest(request["profile"]) != identity.harness_digest: raise ValueError("harness profile differs from attempt manifest") - inspection = inspect_native(request["task_path"], expected_digest=identity.task_digest) + inspection = NATIVE_TASK_PROVIDER.adapter.inspect( + request["task_path"], expected_digest=identity.task_digest + ) if inspection["status"] != "NEEDS_PROBE": raise ValueError("native task preflight failed: " + ",".join(inspection["reasons"])) root = Path(request["output"]).resolve() @@ -51,12 +51,36 @@ async def execute(request, emit, read_command): root.mkdir(parents=True, exist_ok=False) publish(root / "manifest.json", {"identity": identity.to_dict(), "task": inspection, "profile": request["profile"], "gateway_url": request["gateway_url"]}) + sandbox = request.get('sandbox') + agent_gateway = request['gateway_url'] + if sandbox is not None: + from harborrl.rollout.harbor_job.sandbox_config import environment_class + agent_gateway = environment_class(sandbox).agent_gateway(sandbox, agent_gateway) + agent = CLAUDE_CODE_HARNESS.adapter.agent_config( + request['profile'], agent_gateway, request['credential'], request['max_output_tokens']) + environment = {'type': 'docker', 'delete': True} + if sandbox is not None: + from harborrl.rollout.harbor_job.sandbox_config import task_binding + if digest(sandbox) != request.get('runtime_digest'): + raise ValueError('sandbox runtime lock mismatch') + if sandbox['installation'] and sandbox['installation']['version'] != request['profile']['cli_version']: + raise ValueError('offline harness version mismatch') + publish(root / 'runtime.json', {'config': sandbox, 'digest': digest(sandbox)}) + environment = {'type': 'opensandbox', 'delete': True, + 'import_path': sandbox['adapter'], + 'kwargs': {'sandbox_config': sandbox, 'binding': task_binding(sandbox, request['task_path']), + 'evidence_dir': str(root), + 'gateway_url': request['gateway_url'], + 'startup_dir': str(root.parents[2] / '.sandbox-startup')}} + if sandbox['installation']: + agent.pop('name') + agent['import_path'] = 'harborrl.harnesses.offline_claude:OfflineClaudeCode' + agent['kwargs']['installation'] = sandbox['installation'] config = TrialConfig.model_validate({ "task": {"path": inspection["path"]}, "trial_name": digest(identity.to_dict()), "trials_dir": str(root / "harbor"), - "agent": claude_config(request["profile"], request["gateway_url"], request["credential"], - request["max_output_tokens"]), - "environment": {"type": "docker", "delete": True}, + "agent": agent, + "environment": environment, "verifier": {"disable": False, "override_timeout_sec": request["profile"]["verifier_timeout_sec"]}}) trial = await Trial.create(config) trial_id = str(trial.id) diff --git a/harborrl/rollout/harbor_job/sandbox_config.py b/harborrl/rollout/harbor_job/sandbox_config.py new file mode 100644 index 000000000..bb008e184 --- /dev/null +++ b/harborrl/rollout/harbor_job/sandbox_config.py @@ -0,0 +1,135 @@ +"""Non-secret Sandbox settings; no SDK imports in the training process.""" +import asyncio +import copy +import importlib +import re +from contextlib import asynccontextmanager +from pathlib import Path +from urllib.parse import urlsplit + +import yaml + +from harborrl.tasks.content import tomllib + + +def _check_options(value): + if isinstance(value, dict): + for key, item in value.items(): + if not isinstance(key, str): + raise TypeError('sandbox option names must be strings') + name = key.lower().replace('-', '_') + if name in {'token', 'credential', 'password', 'secret', 'authorization'} or name.endswith(('_api_key', '_token', '_secret')) or name == 'api_key': + raise ValueError('sandbox options must reference credential files, not contain secrets') + _check_options(item) + elif isinstance(value, list): + for item in value: + _check_options(item) + + +def load_sandbox(path): + path = Path(path) + cfg = yaml.safe_load(path.read_text()) + required = {'domain', 'credential_file', 'bindings'} + defaults = {'protocol': 'https', 'use_server_proxy': True, + 'request_timeout_s': 60, 'create_timeout_s': 600, 'lifecycle_minutes': 120, + 'cleanup_timeout_s': 120, 'max_live_instances': 2, 'startup_concurrency': 1, + 'adapter': 'harborrl.rollout.harbor_job.environments.opensandbox:ManagedOpenSandboxEnvironment', + 'options': {}, 'installation': None} + if not isinstance(cfg, dict) or not required <= set(cfg) or set(cfg) - required - defaults.keys(): + raise ValueError('invalid sandbox configuration fields') + cfg = {**defaults, **copy.deepcopy(cfg)} + url = urlsplit(cfg['domain']) + if (url.scheme not in ('http', 'https') + or not url.hostname or url.username or url.password or url.query or url.fragment + or url.path.rstrip('/').endswith('/v1') or cfg['protocol'] != url.scheme): + raise ValueError('invalid sandbox domain/protocol') + for key in defaults.keys() - {'protocol', 'use_server_proxy', 'adapter', 'options', 'installation'}: + if type(cfg[key]) is not int or cfg[key] <= 0: + raise ValueError(f'sandbox {key} must be a positive integer') + if type(cfg['use_server_proxy']) is not bool or not isinstance(cfg['options'], dict): + raise ValueError('invalid sandbox proxy/options') + _check_options(cfg['options']) + if not isinstance(cfg['adapter'], str) or not re.fullmatch(r'[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*:[A-Za-z_]\w*', cfg['adapter']): + raise ValueError('sandbox adapter must be module:Class') + if not isinstance(cfg['credential_file'], str) or not cfg['credential_file']: + raise ValueError('sandbox credential_file required') + cfg['credential_file'] = str((path.parent / cfg['credential_file']).resolve()) + bindings = cfg['bindings'] + if not isinstance(bindings, list) or not bindings: + raise ValueError('sandbox image bindings required') + images = set() + for binding in bindings: + if not isinstance(binding, dict) or set(binding) - {'image', 'max_live_instances', 'options'} or not {'image', 'max_live_instances'} <= set(binding): + raise ValueError('invalid sandbox binding fields') + binding.setdefault('options', {}) + _check_options(binding['options']) + if (not isinstance(binding['image'], str) or not binding['image'] + or binding['image'] in images or not isinstance(binding['options'], dict)): + raise ValueError('invalid or duplicate sandbox image binding') + if type(binding['max_live_instances']) is not int or binding['max_live_instances'] <= 0: + raise ValueError('binding max_live_instances must be positive') + images.add(binding['image']) + install = cfg['installation'] + if install is None: + return cfg + if not isinstance(install, dict) or set(install) != {'mode', 'version', 'binary', 'sha256'}: + raise ValueError('installation requires mode, version, binary, sha256') + if (install['mode'] != 'offline_binary' or not isinstance(install['version'], str) + or not re.fullmatch(r'\d+\.\d+\.\d+', install['version']) + or not isinstance(install['sha256'], str) or not re.fullmatch(r'[0-9a-f]{64}', install['sha256']) + or not isinstance(install['binary'], str) or not install['binary']): + raise ValueError('invalid pinned offline binary installation') + install['binary'] = str((path.parent / install['binary']).resolve()) + return cfg + + +def task_binding(cfg, task_path): + spec = tomllib.loads((Path(task_path) / 'task.toml').read_text()) + image = spec.get('environment', {}).get('docker_image') + for binding in cfg['bindings']: + if image == binding['image']: + return binding + raise ValueError('sandbox task requires a prebuilt image with an exact binding') + + +def environment_class(cfg): + """Import trusted deployment code only in the runner, never in GPU training.""" + module, name = cfg['adapter'].split(':') + cls = getattr(importlib.import_module(module), name) + from harborrl.rollout.harbor_job.environments.opensandbox import ( + ManagedOpenSandboxEnvironment, + ) + if not isinstance(cls, type) or not issubclass(cls, ManagedOpenSandboxEnvironment): + raise TypeError('sandbox adapter must implement the audited OpenSandbox lifecycle') + cls.validate_config(cfg) + return cls + + +def read_credential(cfg): + path = Path(cfg['credential_file']) + if path.stat().st_mode & 0o077: + raise ValueError('sandbox credential file must have mode 600 or stricter') + value = path.read_text().strip() + if not value or '\n' in value: + raise ValueError('sandbox credential file must contain one nonempty token') + return value + + +class SandboxAdmission: + """One runtime owns capacity; unresolved cleanup blocks further admission.""" + def __init__(self, cfg): + self.total = asyncio.Semaphore(cfg['max_live_instances']) + self.bindings = {b['image']: asyncio.Semaphore(b['max_live_instances']) for b in cfg['bindings']} + self.blocked = False + + @asynccontextmanager + async def lease(self, binding, root): + # Wait for image-specific capacity before reserving a global live slot. + async with self.bindings[binding['image']], self.total: + if self.blocked: + raise RuntimeError('sandbox admission blocked by unresolved cleanup') + try: + yield + finally: + if (Path(root) / 'cleanup-required.json').exists(): + self.blocked = True diff --git a/harborrl/rollout/harbor_job/sandbox_probe.py b/harborrl/rollout/harbor_job/sandbox_probe.py new file mode 100644 index 000000000..ebed3f47d --- /dev/null +++ b/harborrl/rollout/harbor_job/sandbox_probe.py @@ -0,0 +1,63 @@ +"""Offline dependency check or explicit live Environment check (no allocation).""" +import argparse +import asyncio +import hashlib +import importlib.metadata +import json +import sys +from pathlib import Path + +from harborrl.rollout.harbor_job.sandbox_config import ( + environment_class, + load_sandbox, + read_credential, +) + + +def check(cfg): + from harborrl.rollout.harbor_job.runner import probe + result = probe() + version = importlib.metadata.version('opensandbox') + if version != '0.1.9': + raise ValueError('sandbox runner requires opensandbox==0.1.9') + from opensandbox.models.execd import RunCommandOpts + + adapter = environment_class(cfg) + if not {'timeout', 'envs', 'working_directory'} <= RunCommandOpts.model_fields.keys(): + raise ValueError('unsupported SDK command options') + checksum = None + if cfg['installation']: + with Path(cfg['installation']['binary']).open('rb') as source: + checksum = hashlib.file_digest(source, 'sha256').hexdigest() + if checksum != cfg['installation']['sha256']: + raise ValueError('offline binary checksum mismatch') + read_credential(cfg) + result.update(opensandbox_version=version, environment=adapter.__name__, binary_sha256=checksum) + return result + + +async def network_check(cfg): + return await environment_class(cfg).network_check(cfg) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('config', nargs='?') + parser.add_argument('--stdin', action='store_true') + parser.add_argument('--network', action='store_true') + args = parser.parse_args() + try: + cfg = json.load(sys.stdin) if args.stdin else load_sandbox(args.config) + result = check(cfg) + if args.network: + result['environments'] = asyncio.run(network_check(cfg)) + print(json.dumps(result)) + except Exception as exc: # noqa: BLE001 - redact authenticated SDK exceptions + # SDK/HTTP exceptions may contain authenticated endpoint URLs. + print(json.dumps({'ok': False, 'error_type': type(exc).__name__})) + return 1 + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/harborrl/rollout/harbor_job/sandbox_transport.py b/harborrl/rollout/harbor_job/sandbox_transport.py new file mode 100644 index 000000000..385ad5701 --- /dev/null +++ b/harborrl/rollout/harbor_job/sandbox_transport.py @@ -0,0 +1,27 @@ +"""Observe SDK allocation IDs without retrying creation requests.""" +import re + +import httpx + + +class SandboxStartupTransport(httpx.AsyncBaseTransport): + def __init__(self, base_url, on_created, *, transport=None): + self.base_url = base_url.rstrip('/') + self.on_created = on_created + self.transport = transport or httpx.AsyncHTTPTransport() + + async def handle_async_request(self, request): + url = str(request.url).split('?', 1)[0] + response = await self.transport.handle_async_request(request) + if request.method == 'POST' and url == self.base_url + '/sandboxes' and response.is_success: + await response.aread() + try: + sandbox_id = response.json()['id'] + if isinstance(sandbox_id, str) and re.fullmatch(r'[A-Za-z0-9_-]+', sandbox_id): + self.on_created(sandbox_id) + except (ValueError, KeyError, TypeError): + pass # Let the SDK reject malformed creation responses. + return response + + async def aclose(self): + await self.transport.aclose() diff --git a/harborrl/rollout/native_generate.py b/harborrl/rollout/native_generate.py index 5f59276e7..b5cfeebda 100644 --- a/harborrl/rollout/native_generate.py +++ b/harborrl/rollout/native_generate.py @@ -1,255 +1,17 @@ -"""Slime group hook for native Harbor trials. - -The hook keeps one trajectory per Slime slot and stores its immutable IR in -metadata. Turn expansion happens only at the training converter boundary. -""" -from __future__ import annotations - -import asyncio -import json -import os -import secrets -from pathlib import Path - -from harborrl.rollout.harbor_job.coordinator import GroupSlots, run_attempt -from harborrl.trajectories.native import Identity, RewardProfile, digest, read_json - - -class NativeRolloutRuntime: - def __init__(self, args): - from slime.utils.processing_utils import load_tokenizer - - from harborrl.gateway.server import Gateway, make_server - from harborrl.gateway.sglang_backend import SGLangBackend - from harborrl.gateway.trace import TraceRegistry - - self.args = args - self.run_root = Path(os.environ["RUN_DIR"]).resolve() - self.catalog = read_json(os.environ["HARBORRL_NATIVE_CATALOG"]) - self.profile = read_json(os.environ["HARBORRL_NATIVE_PROFILE"]) - self.rollout_root = self.run_root / "native-rollouts" - self.rollout_root.mkdir(parents=True, exist_ok=True) - self.registry = TraceRegistry(self.run_root / "native-traces") - engine_count = int(args.rollout_num_gpus) // int(args.rollout_num_gpus_per_engine) - limit = max(1, int(args.sglang_server_concurrency) * engine_count) - self.semaphore = asyncio.Semaphore(limit) - tokenizer = load_tokenizer(args.hf_checkpoint, trust_remote_code=True) - tokenizer_digest, template_digest = tokenizer_fingerprint(tokenizer) - expected_tokenizer = os.environ["HARBORRL_NATIVE_TOKENIZER_DIGEST"] - expected_template = os.environ["HARBORRL_NATIVE_TEMPLATE_DIGEST"] - if (tokenizer_digest != expected_tokenizer or template_digest != expected_template): - raise ValueError("live tokenizer/template digest differs from the native lock") - endpoint = f"http://{args.sglang_router_ip}:{args.sglang_router_port}" - backend = SGLangBackend( - endpoint, - tokenizer, - tokenizer_digest=tokenizer_digest, - template_digest=template_digest, - max_context=int(args.rollout_max_context_len), - audited_raw_logprobs=os.environ.get("HARBORRL_NATIVE_AUDITED_LOGPROBS") == "1", - ) - self.backend = backend - gateway = Gateway( - self.registry, - backend, - model=self.profile["model"], - max_output_tokens=int(args.rollout_max_response_len), - ) - self.server = make_server( - gateway, - host=os.environ.get("HARBORRL_NATIVE_GATEWAY_HOST", "0.0.0.0"), - port=int(os.environ["HARBORRL_NATIVE_GATEWAY_PORT"]), - ) - self.thread = asyncio.get_running_loop().run_in_executor(None, self.server.serve_forever) - - def close(self): - self.server.shutdown() - self.server.server_close() - - -def tokenizer_fingerprint(tokenizer): - messages = [{"role": "user", "content": "HarborRL native tokenizer/template fingerprint"}] - kwargs = {"tokenize": True, "add_generation_prompt": True, "enable_thinking": False} - rendered = tokenizer.apply_chat_template(messages, **kwargs) - template = digest({"messages": messages, "kwargs": kwargs, "token_ids": rendered}) - vocabulary = tokenizer.get_vocab() - tokenizer_digest = digest({ - "class": type(tokenizer).__name__, - "vocab": sorted((str(key), int(value)) for key, value in vocabulary.items()), - }) - return tokenizer_digest, template - - -_RUNTIME = None -_RUNTIME_LOCK = asyncio.Lock() - - -async def runtime(args): - global _RUNTIME - if _RUNTIME is None: - async with _RUNTIME_LOCK: - if _RUNTIME is None: - _RUNTIME = NativeRolloutRuntime(args) - return _RUNTIME - - -def publish_native_version(states, expected): - """Open the next admission epoch only after the complete pool is healthy.""" - if _RUNTIME is None: - return - _RUNTIME.registry.drain(float(os.environ.get("HARBORRL_NATIVE_DRAIN_TIMEOUT", "30"))) - _RUNTIME.registry.publish_version(states, str(expected)) - - -def current_policy_version(runtime_): - """Publish and lock the version currently served by the rollout pool.""" - states = runtime_.backend.pool_states() - from harborrl.platform.policy_pool import publish_pool - version = publish_pool(states) - runtime_.registry.drain(float(os.environ.get("HARBORRL_NATIVE_DRAIN_TIMEOUT", "30"))) - runtime_.registry.publish_version(states, version) - return version - - -def close_runtime(): - global _RUNTIME - if _RUNTIME is not None: - _RUNTIME.close() - _RUNTIME = None - - -def _task_entry(sample): - metadata = sample.metadata if isinstance(sample.metadata, dict) else {} - entry = metadata.get("native_task") - required = {"id", "revision", "path", "task_digest", "reward_profile"} - if not isinstance(entry, dict) or set(entry) != required: - raise ValueError("native rollout requires a locked task catalog entry") - return entry - - -def _identity( - args, - sample, - slot, - rollout_id, - task_digest, - reward_digest, - sampling_digest, - policy_version, -): - group_index = getattr(sample, "group_index", None) - if type(group_index) is not int: - raise ValueError("Slime must provide group identity before native generation") - return Identity( - run_id=os.environ["RUN_ID"], - batch_id=f"rollout-{rollout_id}", - group_id=f"group-{group_index}", - slot_id=str(slot), - attempt_id=secrets.token_hex(16), - trajectory_id=secrets.token_hex(16), - task_digest=task_digest, - policy_version=str(policy_version), - harness_digest=digest(read_json(os.environ["HARBORRL_NATIVE_PROFILE"])), - reward_digest=reward_digest, - sampling_digest=sampling_digest, - ) - - -async def _run_slot( - args, - runtime_, - sample, - slot, - rollout_id, - entry, - sampling_digest, - policy_version, -): - reward_profile = RewardProfile(**entry["reward_profile"]) - identities = [ - _identity( - args, - sample, - index, - rollout_id=rollout_id, - task_digest=entry["task_digest"], - reward_digest=reward_profile.digest, - sampling_digest=sampling_digest, - policy_version=policy_version, - ) - for index in range(args.n_samples_per_prompt) - ] - slots = GroupSlots(identities, max_attempts=int(os.environ["HARBORRL_NATIVE_MAX_ATTEMPTS"])) - workers = json.loads(os.environ["HARBORRL_NATIVE_RUNNER_WORKERS"]) - task_workers = json.loads(os.environ.get("HARBORRL_NATIVE_TASK_WORKER_OVERRIDES", "{}")) - for attempt_index in range(slots.max_attempts): - identity = slots.start(str(slot)) - runner_host = task_workers.get(entry["id"]) - if runner_host is None: - runner_host = workers[(slot + attempt_index) % len(workers)] - root = runtime_.rollout_root / identity.group_id / identity.attempt_id - try: - async with runtime_.semaphore: - ir = await run_attempt( - identity, - task_path=entry["path"], - profile=runtime_.profile, - gateway_url=os.environ["HARBORRL_NATIVE_GATEWAY_URL"], - runner_python=os.environ["HARBORRL_NATIVE_RUNNER_PYTHON"], - runner_host=runner_host, - root=root, - registry=runtime_.registry, - reward_profile=reward_profile, - max_output_tokens=int(args.rollout_max_response_len), - ) - except (OSError, ValueError, RuntimeError, asyncio.TimeoutError) as exc: - slots.finish(str(slot), error=f"{type(exc).__name__}: {exc}") - continue - slots.finish(str(slot), ir) - return ir - raise RuntimeError(f"native slot {slot} exhausted its retry budget") - - -async def generate_group(args, group, sampling_params, rollout_id, evaluation=False): - """Run one complete native group; evaluation is intentionally unsupported.""" - from harborrl.data.harbor.native_inspector import inspect_native - - if evaluation: - raise ValueError("native training rollout cannot be reused as evaluation") - if len(group) != args.n_samples_per_prompt: - raise ValueError("native rollout group is incomplete before launch") - entries = [_task_entry(sample) for sample in group] - if any(entry != entries[0] for entry in entries[1:]): - raise ValueError("native rollout group mixes task locks") - entry = entries[0] - report = inspect_native(entry["path"], expected_digest=entry["task_digest"]) - if report["status"] != "NEEDS_PROBE": - raise ValueError(f"native task preflight failed: {report['reasons']}") - stop = list(sampling_params.get("stop") or []) - sampling_digest = digest({ - "temperature": sampling_params.get("temperature"), - "top_p": sampling_params.get("top_p"), - "top_k": sampling_params.get("top_k"), - "max_new_tokens": sampling_params.get("max_new_tokens"), - "stop": stop, - }) - runtime_ = await runtime(args) - policy_version = current_policy_version(runtime_) - trajectories = await asyncio.gather(*[ - _run_slot( - args, runtime_, sample, slot, rollout_id, entry, sampling_digest, policy_version - ) - for slot, sample in enumerate(group) - ]) - output = [] - for sample, ir in zip(group, trajectories): - sample.metadata["native_ir"] = ir - sample.response = "native Harbor trial" - sample.reward = {"score": ir["evaluation"]["training_reward"]} - sample.status = sample.Status.COMPLETED - try: - sample.policy_version = int(ir["identity"]["policy_version"]) - except ValueError as exc: - raise ValueError("native policy version must be an integer in Slime") from exc - output.append(sample) - return output +"""Compatibility imports for the framework-independent rollout runtime.""" +from harborrl.rollout.runtime import ( # noqa: F401 + GroupSlots, + NativeRolloutRuntime, + RolloutRuntime, + RolloutRuntimeConfig, + close_runtime, + current_policy_version, + publish_native_version, + run_attempt, + run_group, + run_slot, + runtime, + slot_identity, + task_entry, + tokenizer_fingerprint, +) diff --git a/harborrl/rollout/runtime.py b/harborrl/rollout/runtime.py new file mode 100644 index 000000000..558bab01d --- /dev/null +++ b/harborrl/rollout/runtime.py @@ -0,0 +1,350 @@ +"""Extracted rollout core (P2): gateway, traces and complete groups. + +This module owns rollout execution and must not import Slime or Ray: training +frameworks live in ``harborrl.backends.*`` adapters that build a +``RolloutRuntimeConfig`` and hand over a tokenizer. Group semantics, worker +scheduling, retries and the RL_READY contract stay exactly as the embedded +pipeline enforced them. +""" +from __future__ import annotations + +import asyncio +import json +import secrets +import threading +from contextlib import nullcontext +from dataclasses import dataclass +from pathlib import Path + +from harborrl.models.qwen import normalize_chat_template_ids +from harborrl.rollout.harbor_job.coordinator import GroupSlots, run_attempt +from harborrl.trajectories.native import Identity, RewardProfile, digest, read_json + +TASK_ENTRY_KEYS = {"id", "revision", "path", "task_digest", "reward_profile"} + + +@dataclass(frozen=True) +class RolloutRuntimeConfig: + """Deployment-independent description of one run's rollout machinery.""" + + run_id: str + run_root: str + catalog_path: str + profile_path: str + gateway_host: str + gateway_port: int + gateway_url: str + runner_python: str + workers: tuple + task_worker_overrides: dict + max_attempts: int + max_output_tokens: int + max_context: int + concurrency_limit: int + group_size: int + expected_tokenizer_digest: str + expected_template_digest: str + audited_raw_logprobs: bool + drain_timeout: float + model_tag: str + endpoint: str + sandbox: dict = None + runner_transport: str = 'ssh' + + @staticmethod + def _required(env, name): + value = env.get(name) + if not isinstance(value, str) or not value.strip(): + raise ValueError(f"rollout runtime requires {name}") + return value + + @classmethod + def from_environment(cls, env, *, endpoint, concurrency_limit, group_size, + gateway_host=None, gateway_port=None, gateway_url=None, + workers=None, runner_python=None): + if not isinstance(endpoint, str) or not endpoint.strip(): + raise ValueError("rollout runtime requires a serving endpoint") + if type(group_size) is not int or group_size < 2: + raise ValueError("rollout runtime requires a group size of at least two") + if workers is None: + workers = json.loads(cls._required(env, "HARBORRL_NATIVE_RUNNER_WORKERS")) + if runner_python is None: + runner_python = cls._required(env, "HARBORRL_NATIVE_RUNNER_PYTHON") + try: + overrides = json.loads(env.get("HARBORRL_NATIVE_TASK_WORKER_OVERRIDES") or "{}") + except ValueError as exc: + raise ValueError("invalid HARBORRL_NATIVE_TASK_WORKER_OVERRIDES") from exc + return cls( + run_id=cls._required(env, "RUN_ID"), + run_root=cls._required(env, "RUN_DIR"), + catalog_path=cls._required(env, "HARBORRL_NATIVE_CATALOG"), + profile_path=cls._required(env, "HARBORRL_NATIVE_PROFILE"), + gateway_host=gateway_host or env.get("HARBORRL_NATIVE_GATEWAY_HOST", "0.0.0.0"), + gateway_port=int(gateway_port or cls._required(env, "HARBORRL_NATIVE_GATEWAY_PORT")), + gateway_url=gateway_url or cls._required(env, "HARBORRL_NATIVE_GATEWAY_URL"), + runner_python=runner_python, + workers=tuple(workers), + task_worker_overrides=dict(overrides), + max_attempts=int(env.get("HARBORRL_NATIVE_MAX_ATTEMPTS", "2")), + max_output_tokens=int(env.get("ROLLOUT_MAX_RESPONSE_LEN", "8192")), + max_context=int(env.get("ROLLOUT_MAX_CONTEXT_LEN", "32768")), + concurrency_limit=max(1, int(concurrency_limit)), + group_size=int(group_size), + expected_tokenizer_digest=cls._required(env, "HARBORRL_NATIVE_TOKENIZER_DIGEST"), + expected_template_digest=cls._required(env, "HARBORRL_NATIVE_TEMPLATE_DIGEST"), + audited_raw_logprobs=env.get("HARBORRL_NATIVE_AUDITED_LOGPROBS") == "1", + drain_timeout=float(env.get("HARBORRL_NATIVE_DRAIN_TIMEOUT", "30")), + model_tag=env.get("MODEL_TAG", env.get("MODEL_ARGS_FILE", "qwen3-8B")), + endpoint=endpoint.rstrip("/"), + sandbox=json.loads(env.get('HARBORRL_NATIVE_SANDBOX', 'null')), + runner_transport=env.get('HARBORRL_NATIVE_RUNNER_TRANSPORT', 'ssh'), + ) + + def environment(self): + """Render the environment mapping this config was built from.""" + return { + "RUN_ID": self.run_id, "RUN_DIR": self.run_root, + "HARBORRL_NATIVE_CATALOG": self.catalog_path, + "HARBORRL_NATIVE_PROFILE": self.profile_path, + "HARBORRL_NATIVE_GATEWAY_HOST": self.gateway_host, + "HARBORRL_NATIVE_GATEWAY_PORT": str(self.gateway_port), + "HARBORRL_NATIVE_GATEWAY_URL": self.gateway_url, + "HARBORRL_NATIVE_RUNNER_PYTHON": self.runner_python, + "HARBORRL_NATIVE_RUNNER_WORKERS": json.dumps(list(self.workers)), + "HARBORRL_NATIVE_TASK_WORKER_OVERRIDES": json.dumps( + self.task_worker_overrides, sort_keys=True), + "HARBORRL_NATIVE_MAX_ATTEMPTS": str(self.max_attempts), + "ROLLOUT_MAX_RESPONSE_LEN": str(self.max_output_tokens), + "ROLLOUT_MAX_CONTEXT_LEN": str(self.max_context), + "HARBORRL_NATIVE_TOKENIZER_DIGEST": self.expected_tokenizer_digest, + "HARBORRL_NATIVE_TEMPLATE_DIGEST": self.expected_template_digest, + "HARBORRL_NATIVE_AUDITED_LOGPROBS": str(int(self.audited_raw_logprobs)), + "HARBORRL_NATIVE_DRAIN_TIMEOUT": str(self.drain_timeout), + "MODEL_TAG": self.model_tag, + "HARBORRL_NATIVE_SANDBOX": json.dumps(self.sandbox), + "HARBORRL_NATIVE_RUNNER_TRANSPORT": self.runner_transport, + } + + +def tokenizer_fingerprint(tokenizer): + messages = [{"role": "user", "content": "HarborRL native tokenizer/template fingerprint"}] + kwargs = {"tokenize": True, "add_generation_prompt": True, "enable_thinking": False} + rendered_ids = normalize_chat_template_ids( + tokenizer.apply_chat_template(messages, **kwargs)) + vocabulary = tokenizer.get_vocab() + tokenizer_digest = digest({ + "class": type(tokenizer).__name__, + "vocab": sorted((str(key), int(value)) for key, value in vocabulary.items()), + }) + template = digest({"messages": messages, "kwargs": kwargs, "token_ids": rendered_ids}) + return tokenizer_digest, template + + +class RolloutRuntime: + """One gateway, one trace registry and one rollout root per run.""" + + def __init__(self, config, tokenizer, *, backend=None): + from harborrl.gateway.server import Gateway, make_server + from harborrl.gateway.sglang_backend import SGLangBackend + from harborrl.gateway.trace import TraceRegistry + from harborrl.math_registry import MATH_REGISTRY + + self.config = config + self.run_root = Path(config.run_root).resolve() + self.catalog = read_json(config.catalog_path) + self.profile = read_json(config.profile_path) + self.rollout_root = self.run_root / "native-rollouts" + self.rollout_root.mkdir(parents=True, exist_ok=True) + self.registry = TraceRegistry(self.run_root / "native-traces") + self.semaphore = asyncio.Semaphore(config.concurrency_limit) + if config.sandbox: + from harborrl.rollout.harbor_job.sandbox_config import SandboxAdmission + self.sandbox_admission = SandboxAdmission(config.sandbox) + tokenizer_digest, template_digest = tokenizer_fingerprint(tokenizer) + if (tokenizer_digest != config.expected_tokenizer_digest + or template_digest != config.expected_template_digest): + raise ValueError("live tokenizer/template digest differs from the native lock") + model = MATH_REGISTRY.model_for_preset(config.model_tag) + self.backend = backend or SGLangBackend( + config.endpoint, tokenizer, model.adapter, + tokenizer_digest=tokenizer_digest, template_digest=template_digest, + max_context=config.max_context, + audited_raw_logprobs=config.audited_raw_logprobs, + ) + gateway = Gateway(self.registry, self.backend, model=self.profile["model"], + max_output_tokens=config.max_output_tokens) + self.server = make_server(gateway, host=config.gateway_host, + port=config.gateway_port) + self._start_gateway() + + def _start_gateway(self): + self.thread = threading.Thread( + target=self.server.serve_forever, name="harborrl-native-gateway", daemon=True + ) + self.thread.start() + + def close(self): + self.server.shutdown() + if getattr(self.thread, "is_alive", lambda: False)(): + self.thread.join(5) + self.server.server_close() + + def manifest_path(self, identity): + """Deterministic shared-storage path of one trajectory manifest.""" + if isinstance(identity, dict): + identity = Identity(**identity) + return self.rollout_root / identity.group_id / identity.attempt_id / "trajectory.v2.json" + + +# Historical name kept for existing hooks and evidence readers. +NativeRolloutRuntime = RolloutRuntime + +_RUNTIME = None +_RUNTIME_LOCK = asyncio.Lock() + + +async def runtime(config, *, tokenizer=None, tokenizer_loader=None): + """Process-wide runtime cache for the embedded GPU-side rollout path.""" + global _RUNTIME + if _RUNTIME is None: + async with _RUNTIME_LOCK: + if _RUNTIME is None: + if tokenizer is None: + if tokenizer_loader is None: + raise ValueError("runtime requires a tokenizer or a loader") + tokenizer = tokenizer_loader() + _RUNTIME = RolloutRuntime(config, tokenizer) + return _RUNTIME + + +def publish_native_version(runtime_, states, expected): + """Open the next admission epoch only after the complete pool is healthy.""" + runtime_.registry.drain(runtime_.config.drain_timeout) + runtime_.registry.publish_version(states, str(expected)) + + +def current_policy_version(runtime_): + """Publish and lock the version currently served by the rollout pool.""" + states = runtime_.backend.pool_states() + from harborrl.platform.policy_pool import publish_pool + version = publish_pool(states) + runtime_.registry.drain(runtime_.config.drain_timeout) + runtime_.registry.publish_version(states, version) + return version + + +def close_runtime(): + global _RUNTIME + if _RUNTIME is not None: + _RUNTIME.close() + _RUNTIME = None + + +def task_entry(sample): + metadata = sample.metadata if isinstance(sample.metadata, dict) else {} + entry = metadata.get("native_task") + if not isinstance(entry, dict) or set(entry) != TASK_ENTRY_KEYS: + raise ValueError("native rollout requires a locked task catalog entry") + return entry + + +def slot_identity(config, group_index, slot, rollout_id, entry, + reward_digest, sampling_digest, policy_version, *, harness_digest=None): + return Identity( + run_id=config.run_id, + batch_id=f"rollout-{rollout_id}", + group_id=f"group-{group_index}", + slot_id=str(slot), + attempt_id=secrets.token_hex(16), + trajectory_id=secrets.token_hex(16), + task_digest=entry["task_digest"], + policy_version=str(policy_version), + harness_digest=harness_digest if harness_digest is not None else digest(read_json(config.profile_path)), + reward_digest=reward_digest, + sampling_digest=sampling_digest, + ) + + +def _group_slots(config, runtime_, group_index, rollout_id, entry, + sampling_digest, policy_version, size): + reward_profile = RewardProfile(**entry["reward_profile"]) + harness_digest = digest(runtime_.profile) + identities = [ + slot_identity(config, group_index, index, rollout_id, entry, + reward_profile.digest, sampling_digest, policy_version, + harness_digest=harness_digest) + for index in range(size) + ] + return GroupSlots(identities, max_attempts=config.max_attempts), reward_profile + + +async def run_slot(config, runtime_, group_index, slot, rollout_id, entry, + sampling_digest, policy_version): + """Compatibility entry point for executing an individual group slot.""" + slots, reward_profile = _group_slots( + config, runtime_, group_index, rollout_id, entry, + sampling_digest, policy_version, config.group_size) + return await _run_slot(config, runtime_, slots, slot, entry, reward_profile) + + +async def _run_slot(config, runtime_, slots, slot, entry, reward_profile): + """Execute one slot with the group's shared identity and retry bookkeeping.""" + workers = config.workers + overrides = config.task_worker_overrides + for attempt_index in range(slots.max_attempts): + identity = slots.start(str(slot)) + runner_host = overrides.get(entry["id"]) + if config.runner_transport == 'local': + runner_host = None + elif runner_host is None: + if not workers: + raise ValueError("rollout runtime requires at least one worker destination") + runner_host = workers[(slot + attempt_index) % len(workers)] + root = runtime_.rollout_root / identity.group_id / identity.attempt_id + admission = nullcontext() + if config.sandbox: + from harborrl.rollout.harbor_job.sandbox_config import task_binding + admission = runtime_.sandbox_admission.lease(task_binding(config.sandbox, entry['path']), root) + try: + async with admission, runtime_.semaphore: + ir = await run_attempt( + identity, + task_path=entry["path"], + profile=runtime_.profile, + gateway_url=config.gateway_url, + runner_python=config.runner_python, + runner_host=runner_host, + root=root, + registry=runtime_.registry, + reward_profile=reward_profile, + max_output_tokens=config.max_output_tokens, + **({'sandbox': config.sandbox} if config.sandbox else {}), + ) + except (OSError, ValueError, RuntimeError, asyncio.TimeoutError) as exc: + slots.finish(str(slot), error=f"{type(exc).__name__}: {exc}") + if config.sandbox and ((root / 'cleanup-required.json').exists() or runtime_.sandbox_admission.blocked): + raise RuntimeError('sandbox cleanup unresolved; automatic retry blocked') from exc + continue + slots.finish(str(slot), ir) + return ir + raise RuntimeError(f"native slot {slot} exhausted its retry budget") + + +async def run_group(config, runtime_, *, entry, group_index, rollout_id, + sampling_digest, policy_version, group_size=None): + """Execute one complete group and return per-slot IRs in slot order.""" + size = config.group_size if group_size is None else int(group_size) + slots, reward_profile = _group_slots( + config, runtime_, group_index, rollout_id, entry, + sampling_digest, policy_version, size) + tasks = [asyncio.create_task( + _run_slot(config, runtime_, slots, slot, entry, reward_profile)) + for slot in range(size) + ] + try: + return list(await asyncio.gather(*tasks)) + except BaseException: + for task in tasks: + task.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + raise diff --git a/harborrl/rollout/service.py b/harborrl/rollout/service.py new file mode 100644 index 000000000..e223ab05a --- /dev/null +++ b/harborrl/rollout/service.py @@ -0,0 +1,562 @@ +"""RolloutService (P3): the manager-side HTTP control plane for rollout groups. + +First-phase contract: HTTP JSON, run-scoped tokens, idempotent group +submission. Bulky payloads (trajectory manifests, token ids, logprobs, loss +masks) live on the shared run directory; HTTP responses carry paths plus +SHA-256 digests, and the SlimeAdapter recomputes every digest before +converting anything into training samples. + +Rules that keep the lifecycle safe: +- same group id and same content -> the existing state, never a second run; +- same group id and different content -> rejected; +- after a timeout the client must query, never resubmit; +- a new policy version is admitted only after the whole serving pool is + healthy on it (drain first, publish second). +""" +from __future__ import annotations + +import asyncio +import hashlib +import hmac +import json +import secrets +import threading +import time +from dataclasses import dataclass, field +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from urllib.parse import urlsplit + +from harborrl.rollout.exporters.native import export_group +from harborrl.rollout.runtime import run_group +from harborrl.trajectories.native import digest, finite, publish, read_json + +SERVICE_SCHEMA = "harborrl-rollout-service-v1" +GROUP_REQUEST_KEYS = {"group_id", "rollout_id", "group_index", "policy_version", + "task", "sampling", "slots"} +SAMPLING_KEYS = {"temperature", "top_p", "top_k", "max_new_tokens", "stop"} +TASK_ENTRY_KEYS = {"id", "revision", "path", "task_digest", "reward_profile"} +GROUP_DIR = "rollout-service/groups" + + +class ServiceError(Exception): + """Maps onto one HTTP status; carries a machine-readable code.""" + + def __init__(self, status, code, message): + super().__init__(message) + self.status, self.code = status, code + + +def validate_group_request(request): + if not isinstance(request, dict) or set(request) != GROUP_REQUEST_KEYS: + raise ServiceError(400, "invalid_request", + f"group request requires exactly {sorted(GROUP_REQUEST_KEYS)}") + for key in ("group_id", "policy_version"): + if not isinstance(request[key], str) or not request[key].strip(): + raise ServiceError(400, "invalid_request", f"{key} must be a nonempty string") + for key in ("rollout_id", "group_index"): + if type(request[key]) is not int or request[key] < 0: + raise ServiceError(400, "invalid_request", f"{key} must be a nonnegative integer") + task = request["task"] + if not isinstance(task, dict) or set(task) != TASK_ENTRY_KEYS: + raise ServiceError(400, "invalid_request", + f"task requires exactly {sorted(TASK_ENTRY_KEYS)}") + if not isinstance(task["reward_profile"], dict): + raise ServiceError(400, "invalid_request", "task.reward_profile must be a mapping") + sampling = request["sampling"] + if not isinstance(sampling, dict) or set(sampling) != SAMPLING_KEYS: + raise ServiceError(400, "invalid_request", + f"sampling requires exactly {sorted(SAMPLING_KEYS)}") + if not finite(sampling["temperature"]) or not finite(sampling["top_p"]): + raise ServiceError(400, "invalid_request", "sampling probabilities must be finite") + if type(sampling["top_k"]) is not int: + raise ServiceError(400, "invalid_request", "sampling.top_k must be an integer") + if type(sampling["max_new_tokens"]) is not int or sampling["max_new_tokens"] < 1: + raise ServiceError(400, "invalid_request", "sampling.max_new_tokens must be positive") + if (not isinstance(sampling["stop"], list) + or any(not isinstance(stop, str) or not stop for stop in sampling["stop"])): + raise ServiceError(400, "invalid_request", "sampling.stop must be a string list") + slots = request["slots"] + if (not isinstance(slots, list) or len(slots) < 2 + or any(not isinstance(slot, dict) or set(slot) != {"slot_id"} + or not isinstance(slot["slot_id"], str) or not slot["slot_id"].strip() + for slot in slots)): + raise ServiceError(400, "invalid_request", + "slots must describe at least two unique slot ids") + if len({slot["slot_id"] for slot in slots}) != len(slots): + raise ServiceError(400, "invalid_request", "slot ids must be unique") + return request + + +def request_digest(request): + """Canonical digest of the full request content, group id included.""" + return digest(request) + + +def manifest_path_from(run_root, identity): + """Deterministic trajectory manifest path for one identity.""" + return Path(run_root) / "native-rollouts" / identity["group_id"] / \ + identity["attempt_id"] / "trajectory.v2.json" + + +def build_group_result(run_id, request, trajectories, *, runtime, export=None): + if len(trajectories) != len(request["slots"]): + raise ServiceError(500, "incomplete_group", + "executor returned a different slot count") + slots = [] + for descriptor, ir in zip(request["slots"], trajectories): + identity = ir["identity"] + path = runtime.manifest_path(identity) + payload = read_json(path) + seal = ir["trace_seal"] + slots.append({ + "slot_id": descriptor["slot_id"], + "trajectory_id": identity["trajectory_id"], + "attempt_id": identity["attempt_id"], + "manifest_path": str(path), + "manifest_sha256": digest(payload), + "finish_reason": ir["termination"], + "reward": ir["evaluation"]["training_reward"], + "policy_version": identity["policy_version"], + "task_digest": identity["task_digest"], + "harness_digest": identity["harness_digest"], + "sampling_digest": identity["sampling_digest"], + "readiness": ir["readiness"], + "integrity_errors": ir["integrity_errors"], + "trace_seal": {"status": seal["status"], "errors": seal["errors"], + "digest": digest(seal)}, + "verifier_digest": ir["evaluation"].get("source_result_digest"), + }) + result = { + "schema_version": SERVICE_SCHEMA, + "run_id": run_id, + "group_id": request["group_id"], + "rollout_id": request["rollout_id"], + "request_digest": request_digest(request), + "policy_version": request["policy_version"], + "status": "complete", + "slots": slots, + "completed_at": time.time(), + } + if export is not None: + result["group_export"] = export + return result + + +async def default_executor(config, runtime_, request): + """The production executor: the framework-free rollout core.""" + return await run_group( + config, runtime_, entry=request["task"], group_index=request["group_index"], + rollout_id=request["rollout_id"], sampling_digest=digest(request["sampling"]), + policy_version=request["policy_version"], group_size=len(request["slots"])) + + +@dataclass +class GroupState: + request: dict + request_sha: str + status: str = "queued" + result: dict = None + error: str = None + created_at: float = field(default_factory=time.time) + future: object = None + + def summary(self, *, include_result=True): + payload = {"group_id": self.request["group_id"], "status": self.status, + "request_digest": self.request_sha, + "created_at": self.created_at} + if self.error: + payload["error"] = self.error + if include_result and self.result is not None: + payload["result"] = self.result + return payload + + +@dataclass +class RunState: + run_id: str + manifest: dict + manifest_sha: str + token_digest: str + config: object = None + runtime: object = None + drained: bool = False + serving: str = None + groups: dict = field(default_factory=dict) + + +class RolloutService: + """Run registry plus a single-threaded asyncio executor for group work.""" + + def __init__(self, *, run_root, executor=None, runtime_factory=None, + token_file=None, group_wait_timeout=3600.0, poll_interval=0.05, + allow_synthetic=False): + self.run_root = Path(run_root).resolve() + self.executor = executor or default_executor + self.runtime_factory = runtime_factory + self.allow_synthetic = allow_synthetic + self.token = None + if token_file is not None: + token = Path(token_file).read_text().strip() + if not token: + raise ServiceError(500, "invalid_token_file", "run token file is empty") + self.token = token + self.group_wait_timeout = group_wait_timeout + self.poll_interval = poll_interval + self.runs = {} + self.lock = threading.RLock() + self.loop = asyncio.new_event_loop() + self.thread = threading.Thread(target=self._serve_loop, + name="harborrl-rollout-service", daemon=True) + self.thread.start() + + def _serve_loop(self): + asyncio.set_event_loop(self.loop) + self.loop.run_forever() + + def close(self): + with self.lock: + states = [state for state in self.runs.values() if state.runtime is not None] + for state in states: + try: + state.runtime.close() + except AttributeError: + pass + self.loop.call_soon_threadsafe(self.loop.stop) + self.thread.join(5) + + # -- run lifecycle ---------------------------------------------------- + + def _token_digest(self, token): + return hashlib.sha256(token.encode()).hexdigest() + + def register_run(self, run_id, manifest, *, token=None): + if not isinstance(run_id, str) or not run_id.strip(): + raise ServiceError(400, "invalid_request", "run id required") + if not isinstance(manifest, dict) or not manifest: + raise ServiceError(400, "invalid_request", "manifest must be a nonempty mapping") + if manifest.get("run_id", run_id) != run_id: + raise ServiceError(400, "invalid_request", "manifest run id must match the path") + manifest_sha = digest(manifest) + with self.lock: + existing = self.runs.get(run_id) + if existing is not None: + if existing.manifest_sha != manifest_sha: + raise ServiceError(409, "conflict", + "run id already registered with a different manifest") + return {"run_id": run_id, "state": "registered", "existed": True} + if self.token is not None and token != self.token: + raise ServiceError(401, "unauthorized", "invalid manager run token") + generated = token is None + if generated: + token = secrets.token_urlsafe(32) + self.runs[run_id] = RunState(run_id=run_id, manifest=manifest, + manifest_sha=manifest_sha, + token_digest=self._token_digest(token)) + return {"run_id": run_id, "state": "registered", "existed": False, + **({"run_token": token} if generated else {})} + + def authenticate(self, run_id, token): + with self.lock: + state = self.runs.get(run_id) + if state is None: + raise ServiceError(404, "not_found", f"unknown run: {run_id}") + if not isinstance(token, str) or not token or not hmac.compare_digest( + state.token_digest, self._token_digest(token)): + raise ServiceError(401, "unauthorized", "invalid run token") + return state + + def _run(self, run_id): + state = self.runs.get(run_id) + if state is None: + raise ServiceError(404, "not_found", f"unknown run: {run_id}") + return state + + def attach_runtime(self, run_id, config, runtime): + """Bind the rollout runtime that executes this run's groups.""" + with self.lock: + state = self._run(run_id) + state.config, state.runtime = config, runtime + return {"run_id": run_id, "state": "attached"} + + def announce_serving(self, run_id, router_url): + parts = urlsplit(router_url or "") + if (parts.scheme not in ("http", "https") or not parts.hostname + or parts.username or parts.password or parts.query + or parts.fragment or parts.path not in ("", "/")): + raise ServiceError(400, "invalid_request", "router_url must be a plain origin") + with self.lock: + state = self._run(run_id) + state.serving = f"{parts.scheme}://{parts.netloc}".rstrip("/") + if state.runtime is None and self.runtime_factory is not None: + runtime = self.runtime_factory(run_id, state.serving) + state.runtime = runtime + if state.config is None: + # The factory-built runtime carries its own config; the + # executor needs both for group admission. + state.config = getattr(runtime, "config", None) + return {"run_id": run_id, "router_url": state.serving, + "runtime": "attached" if state.runtime is not None else "pending"} + + # -- policy lifecycle (§7) --------------------------------------------- + + def activate_policy(self, run_id, states, expected): + from harborrl.platform.policy_pool import validate_pool + + with self.lock: + state = self._run(run_id) + if state.runtime is None: + raise ServiceError(409, "admission_closed", "run has no serving runtime yet") + try: + version = validate_pool(states, str(expected)) + backend = getattr(state.runtime, 'backend', None) + if backend is not None: + live_states = backend.pool_states() + validate_pool(live_states, version) + if {item['endpoint'] for item in live_states} != {item['endpoint'] for item in states}: + raise ValueError('manager and GPU disagree on serving engine membership') + states = live_states + except (RuntimeError, TypeError, ValueError) as exc: + raise ServiceError(409, "pool_invalid", str(exc)) from exc + registry = state.runtime.registry + registry.drain(float(getattr(state.config, "drain_timeout", 30))) + registry.publish_version(states, version) + # Mirror policy_pool.publish_pool into the shared run directory so the + # GPU side can lock the same version without manager-local state; the + # record is replaceable because activation legitimately advances. + payload = {"weight_version": version, "engines": states, "timestamp": time.time()} + temp = self.run_root / "policy-pool.tmp" + temp.write_text(json.dumps(payload) + "\n") + temp.replace(self.run_root / "policy-pool.json") + return {"run_id": run_id, "policy_version": version, "admission": "open"} + + # -- group lifecycle ---------------------------------------------------- + + def _group_dir(self, run_id, group_id): + return self.run_root / GROUP_DIR / run_id / group_id + + def submit_group(self, run_id, request, *, wait=True): + request = validate_group_request(request) + sha = request_digest(request) + with self.lock: + state = self._run(run_id) + existing = state.groups.get(request["group_id"]) + if existing is not None: + if existing.request_sha != sha: + raise ServiceError(409, "conflict", + "group id already submitted with different content") + group = existing + else: + if state.drained: + raise ServiceError(409, "admission_closed", "run is draining") + if state.runtime is None or state.config is None: + raise ServiceError(409, "admission_closed", + "run has no rollout runtime attached") + registry = getattr(state.runtime, "registry", None) + version = getattr(registry, "version", None) if registry is not None else None + accepting = getattr(registry, "accepting", True) if registry is not None else False + if not accepting or version != request["policy_version"]: + raise ServiceError( + 409, "stale_policy", + f"policy {request['policy_version']!r} is not the admitted version " + f"({version!r}, accepting={accepting}); activate first") + group = GroupState(request=request, request_sha=sha) + state.groups[request["group_id"]] = group + future = asyncio.run_coroutine_threadsafe( + self._execute(state, group), self.loop) + group.future = future + if wait: + return self._wait_group(run_id, group) + return group.summary(include_result=False) + + def _wait_group(self, run_id, group): + deadline = time.monotonic() + self.group_wait_timeout + while group.status in ("queued", "running"): + if time.monotonic() >= deadline: + return {**group.summary(include_result=False), + "hint": "query GET /groups/{group_id}; do not resubmit"} + time.sleep(self.poll_interval) + return group.summary() + + async def _execute(self, state, group): + # Immutable state records: each transition gets its own file, and + # publish() refuses to rewrite history. + request = group.request + directory = self._group_dir(state.run_id, request["group_id"]) + publish(directory / "request.json", request) + group.status = "running" + publish(directory / "running.json", group.summary(include_result=False)) + try: + trajectories = await self.executor(state.config, state.runtime, request) + export = export_group( + trajectories, len(trajectories), + allow_synthetic=self.allow_synthetic) + export_path = directory / "group-export.json" + publish(export_path, export) + group.result = build_group_result( + state.run_id, request, trajectories, runtime=state.runtime, + export={"path": str(export_path), "sha256": digest(export)}) + group.status = "complete" + publish(directory / "result.json", group.result) + except asyncio.CancelledError: + group.status = "cancelled" + publish(directory / "cancelled.json", group.summary(include_result=False)) + raise + except Exception as exc: # noqa: BLE001 — any executor failure is + # recorded as a failed group instead of killing the service + group.status = "failed" + group.error = f"{type(exc).__name__}: {exc}" + publish(directory / "failed.json", group.summary(include_result=False)) + + def group_status(self, run_id, group_id): + with self.lock: + state = self._run(run_id) + group = state.groups.get(group_id) + if group is None: + raise ServiceError(404, "not_found", f"unknown group: {group_id}") + return group.summary() + + def cancel_group(self, run_id, group_id): + with self.lock: + state = self._run(run_id) + group = state.groups.get(group_id) + if group is None: + raise ServiceError(404, "not_found", f"unknown group: {group_id}") + self._cancel_groups([group]) + return group.summary() + + def drain_run(self, run_id): + with self.lock: + state = self._run(run_id) + state.drained = True + runtime = state.runtime + if runtime is not None: + registry = getattr(runtime, "registry", None) + if registry is not None: + timeout = float(getattr(state.config, "drain_timeout", 30)) + try: + registry.drain(timeout) + except TimeoutError as exc: + raise ServiceError(409, "drain_timeout", + f"generation still in flight: {exc}") from exc + return {"run_id": run_id, "state": "drained"} + + def cancel_run(self, run_id): + with self.lock: + state = self._run(run_id) + groups = list(state.groups.values()) + self._cancel_groups(groups) + return {"run_id": run_id, "state": "cancelled", + "groups": {group.request["group_id"]: group.status for group in groups}} + + def _cancel_groups(self, groups): + for group in groups: + if group.status in ("queued", "running") and group.future is not None: + group.future.cancel() + deadline = time.monotonic() + 30 + while any(group.status in ("queued", "running") for group in groups) \ + and time.monotonic() < deadline: + time.sleep(self.poll_interval) + + +def make_control_server(service, host="127.0.0.1", port=0, *, max_body_bytes=8 * 1024 * 1024): + """Bounded stdlib HTTP transport for the control plane.""" + + class Handler(BaseHTTPRequestHandler): + def setup(self): + super().setup() + self.connection.settimeout(120) + + def log_message(self, *args): + pass + + def reply(self, status, payload): + body = json.dumps(payload).encode() + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def token(self): + token = self.headers.get("x-api-key", "") + auth = self.headers.get("Authorization", "") + if auth: + if not auth.startswith("Bearer ") or token: + raise ServiceError(400, "invalid_request", "ambiguous authentication") + token = auth[7:] + return token + + def read_json(self): + if self.headers.get("Transfer-Encoding"): + raise ServiceError(400, "invalid_request", "chunked bodies are not supported") + size = int(self.headers.get("Content-Length", "0")) + if size <= 0 or size > max_body_bytes: + raise ServiceError(400, "invalid_request", "invalid request body size") + return json.loads(self.rfile.read(size)) + + def dispatch(self, method): + parts = [part for part in urlsplit(self.path).path.split("/") if part] + try: + payload = self.route(method, parts) + except ServiceError as exc: + self.reply(exc.status, {"error": {"type": exc.code, "message": str(exc)}}) + except (ValueError, TypeError, KeyError, OSError, json.JSONDecodeError) as exc: + self.reply(400, {"error": {"type": "invalid_request", + "message": f"{type(exc).__name__}: {exc}"}}) + else: + self.reply(200, payload) + + def route(self, method, parts): + if method == "GET" and parts == ["healthz"]: + return {"ok": True, "service": SERVICE_SCHEMA} + if method == "GET" and parts == ["readyz"]: + with service.lock: + attached = sum(1 for state in service.runs.values() + if state.runtime is not None) + return {"ok": True, "runs": len(service.runs), "attached_runs": attached} + if len(parts) < 2 or parts[0] != "runs": + raise ServiceError(404, "not_found", "unsupported endpoint") + run_id = parts[1] + if len(parts) == 3 and method == "POST" and parts[2] == "register": + body = self.read_json() + if not isinstance(body, dict) or set(body) != {"manifest"}: + raise ServiceError(400, "invalid_request", "body must be {manifest}") + return service.register_run(run_id, body["manifest"], token=self.token()) + service.authenticate(run_id, self.token()) + if len(parts) == 3 and method == "POST" and parts[2] == "groups": + return service.submit_group(run_id, self.read_json()) + if (len(parts) == 4 and parts[2] == "groups" + and method == "GET"): + return service.group_status(run_id, parts[3]) + if (len(parts) == 5 and parts[2] == "groups" and parts[4] == "cancel" + and method == "POST"): + return service.cancel_group(run_id, parts[3]) + if len(parts) == 4 and method == "POST" and parts[2] == "policy" \ + and parts[3] == "activate": + body = self.read_json() + if not isinstance(body, dict) or set(body) != {"states", "expected"}: + raise ServiceError(400, "invalid_request", + "body must be {states, expected}") + return service.activate_policy(run_id, body["states"], body["expected"]) + if len(parts) == 4 and method == "POST" and parts[2] == "serving" \ + and parts[3] == "announce": + body = self.read_json() + if not isinstance(body, dict) or set(body) != {"router_url"}: + raise ServiceError(400, "invalid_request", "body must be {router_url}") + return service.announce_serving(run_id, body["router_url"]) + if len(parts) == 3 and method == "POST" and parts[2] == "drain": + return service.drain_run(run_id) + if len(parts) == 3 and method == "POST" and parts[2] == "cancel": + return service.cancel_run(run_id) + raise ServiceError(404, "not_found", "unsupported endpoint") + + def do_GET(self): + self.dispatch("GET") + + def do_POST(self): + self.dispatch("POST") + + server = ThreadingHTTPServer((host, port), Handler) + server.daemon_threads = True + return server diff --git a/harborrl/tasks/catalog.py b/harborrl/tasks/catalog.py new file mode 100644 index 000000000..d887e7ec6 --- /dev/null +++ b/harborrl/tasks/catalog.py @@ -0,0 +1,8 @@ +"""Compatibility entry point for the selected Harbor task provider.""" +from __future__ import annotations + +from harborrl.math_registry import select_math + + +def catalog(config): + return select_math(config).task.adapter.catalog(config) diff --git a/harborrl/tasks/cli.py b/harborrl/tasks/cli.py index 8cbb3c5d3..f9d337cd3 100644 --- a/harborrl/tasks/cli.py +++ b/harborrl/tasks/cli.py @@ -7,7 +7,7 @@ from pathlib import Path from uuid import uuid4 -from harborrl.data.harbor.native_inspector import inspect_native +from harborrl.math_registry import NATIVE_TASK_PROVIDER def main(argv=None): @@ -23,12 +23,12 @@ def main(argv=None): if args.command == "inspect": if not args.tasks_dir.is_dir(): parser.error("tasks-dir must exist") - tasks = sorted(path.parent for path in args.tasks_dir.rglob("task.toml")) + tasks = NATIVE_TASK_PROVIDER.adapter.discover(args.tasks_dir) if not tasks: parser.error("no task.toml found") rows = [] for task in tasks: - result = inspect_native(task) + result = NATIVE_TASK_PROVIDER.adapter.inspect(task) rows.append({ **result, "dataset": args.dataset, diff --git a/harborrl/tasks/content.py b/harborrl/tasks/content.py new file mode 100644 index 000000000..e108f167e --- /dev/null +++ b/harborrl/tasks/content.py @@ -0,0 +1,26 @@ +"""Native task content hashing and TOML reader shared with compatibility tools.""" +import hashlib +from pathlib import Path + +try: + import tomllib +except ModuleNotFoundError: # Python 3.10 + import tomli as tomllib + + +__all__ = ["tomllib", "tree_digest"] + + +def tree_digest(root: Path, *, exclude: tuple[str, ...] = ()) -> str: + h = hashlib.sha256() + for path in sorted(Path(root).rglob("*")): + if str(path.relative_to(root)) in exclude: + continue + if path.is_symlink(): + raise ValueError("symlinks are not supported") + if path.is_file(): + data = path.read_bytes() + h.update(str(path.relative_to(root)).encode() + b"\0") + h.update(str(path.stat().st_mode & 0o777).encode() + b"\0") + h.update(str(len(data)).encode() + b"\0" + data) + return h.hexdigest() diff --git a/harborrl/tasks/jsonl_provider.py b/harborrl/tasks/jsonl_provider.py new file mode 100644 index 000000000..4fdeae0fd --- /dev/null +++ b/harborrl/tasks/jsonl_provider.py @@ -0,0 +1,92 @@ +"""JSONL Harbor task catalog provider with the same native task lock contract.""" +from __future__ import annotations + +import json +import re +from pathlib import Path + +from harborrl.data.harbor.native_inspector import inspect_native +from harborrl.trajectories.native import RewardProfile + + +class JsonlTaskProvider: + provider_id = "harbor_jsonl" + + def discover(self, tasks_dir): + return sorted(path.parent for path in Path(tasks_dir).rglob("task.toml")) + + def inspect(self, path, *, expected_digest=None): + return inspect_native(path, expected_digest=expected_digest) + + def catalog(self, config): + path = Path(config["tasks"]["catalog"]) + entries, seen = [], set() + with path.open(encoding="utf-8") as source: + for line_number, line in enumerate(source, start=1): + if not line.strip(): + raise ValueError(f"blank JSONL catalog line: {line_number}") + try: + entry = json.loads(line) + except json.JSONDecodeError as exc: + raise ValueError( + f"invalid JSONL catalog line {line_number}: {exc.msg}" + ) from exc + if ( + not isinstance(entry, dict) + or set(entry) + != {"id", "revision", "path", "task_digest", "reward_profile"} + or not isinstance(entry["id"], str) + or not entry["id"].strip() + or not isinstance(entry["revision"], str) + or not entry["revision"].strip() + or entry["id"] in seen + ): + raise ValueError( + f"invalid or duplicate JSONL catalog identity: {line_number}" + ) + if ( + not isinstance(entry["path"], str) + or not entry["path"].strip() + or not isinstance(entry["task_digest"], str) + or not re.fullmatch(r"[0-9a-f]{64}", entry["task_digest"]) + or not isinstance(entry["reward_profile"], dict) + ): + raise ValueError(f'invalid native task lock: {entry["id"]}') + seen.add(entry["id"]) + entry["path"] = str((path.parent / entry["path"]).resolve()) + report = self.inspect( + entry["path"], expected_digest=entry["task_digest"] + ) + if report["status"] != "NEEDS_PROBE": + raise ValueError( + f'task preflight failed: {entry["id"]}: {report["reasons"]}' + ) + RewardProfile(**entry["reward_profile"]) + entries.append(entry) + if not entries: + raise ValueError("catalog must contain at least one JSONL task lock") + selected = set(getattr(config, "task_ids", ())) + if selected: + missing = selected - {entry["id"] for entry in entries} + if missing: + raise ValueError( + f"task selector not found in catalog: {min(missing)}" + ) + entries = [entry for entry in entries if entry["id"] in selected] + return entries + + def prompt_rows(self, entries): + rows = [] + for entry in entries: + instruction = (Path(entry["path"]) / "instruction.md").read_text() + rows.append( + { + "task": instruction, + "metadata": { + "harborrl_native": True, + "task_provider": self.provider_id, + "native_task": entry, + }, + } + ) + return rows diff --git a/harborrl/tasks/provider.py b/harborrl/tasks/provider.py new file mode 100644 index 000000000..239fd58ef --- /dev/null +++ b/harborrl/tasks/provider.py @@ -0,0 +1,72 @@ +"""Native Harbor task discovery, locking, and prompt materialization.""" +from __future__ import annotations + +import re +from pathlib import Path + +from harborrl.data.harbor.native_inspector import inspect_native +from harborrl.trajectories.native import RewardProfile, read_json + + +class NativeTaskProvider: + provider_id = "native_harbor" + + def discover(self, tasks_dir): + return sorted(path.parent for path in Path(tasks_dir).rglob("task.toml")) + + def inspect(self, path, *, expected_digest=None): + return inspect_native(path, expected_digest=expected_digest) + + def catalog(self, config): + path = Path(config["tasks"]["catalog"]) + entries = read_json(path) + if not isinstance(entries, list) or not entries: + raise ValueError("catalog must be a nonempty JSON list") + seen = set() + for entry in entries: + if ( + not isinstance(entry, dict) + or set(entry) + != {"id", "revision", "path", "task_digest", "reward_profile"} + or not isinstance(entry["id"], str) + or not entry["id"].strip() + or not isinstance(entry["revision"], str) + or not entry["revision"].strip() + or entry["id"] in seen + ): + raise ValueError("invalid or duplicate catalog identity") + if ( + not isinstance(entry["path"], str) + or not entry["path"].strip() + or not isinstance(entry["task_digest"], str) + or not re.fullmatch(r"[0-9a-f]{64}", entry["task_digest"]) + or not isinstance(entry["reward_profile"], dict) + ): + raise ValueError(f'invalid native task lock: {entry["id"]}') + seen.add(entry["id"]) + entry["path"] = str((path.parent / entry["path"]).resolve()) + report = self.inspect(entry["path"], expected_digest=entry["task_digest"]) + if report["status"] != "NEEDS_PROBE": + raise ValueError( + f'task preflight failed: {entry["id"]}: {report["reasons"]}' + ) + RewardProfile(**entry["reward_profile"]) + selected = set(getattr(config, "task_ids", ())) + if selected: + missing = selected - {entry["id"] for entry in entries} + if missing: + raise ValueError(f"task selector not found in catalog: {min(missing)}") + entries = [entry for entry in entries if entry["id"] in selected] + return entries + + def prompt_rows(self, entries): + rows = [] + for entry in entries: + instruction = (Path(entry["path"]) / "instruction.md").read_text() + rows.append( + { + "task": instruction, + "metadata": {"harborrl_native": True, "native_task": entry}, + } + ) + return rows diff --git a/harborrl/webui/__init__.py b/harborrl/webui/__init__.py new file mode 100644 index 000000000..765ec5b8e --- /dev/null +++ b/harborrl/webui/__init__.py @@ -0,0 +1 @@ +"""Local configuration and training console; runtime imports stay lazy.""" diff --git a/harborrl/webui/cli.py b/harborrl/webui/cli.py new file mode 100644 index 000000000..1193ce5d8 --- /dev/null +++ b/harborrl/webui/cli.py @@ -0,0 +1,67 @@ +"""Start a single-user loopback console, optionally opening the browser.""" +import argparse +import contextlib +import os +import signal +import webbrowser +from pathlib import Path + + +@contextlib.contextmanager +def workspace_lock(workspace): + workspace.mkdir(parents=True, exist_ok=True) + with (workspace / ".console.lock").open("a+b") as stream: + try: + if os.name == "posix": + import fcntl + fcntl.flock(stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + else: + import msvcrt + stream.seek(0) + if stream.read(1) == b"": + stream.write(b"0") + stream.flush() + stream.seek(0) + msvcrt.locking(stream.fileno(), msvcrt.LK_NBLCK, 1) + except OSError as exc: + raise ValueError("Another console owns this workspace") from exc + yield + + +def main(argv): + parser = argparse.ArgumentParser(prog="harborrl web", description=__doc__) + parser.add_argument("--workspace", default="./harborrl-web", help="Configurations and job logs directory") + parser.add_argument("--port", type=int, default=0, help="Loopback port; 0 selects a free port") + parser.add_argument("--no-browser", action="store_true", help="Print the URL without opening a browser") + args = parser.parse_args(argv) + if not 0 <= args.port <= 65535: + parser.error("port must be in 0..65535") + from .server import Console, make_server + + workspace = Path(args.workspace).expanduser().resolve() + try: + with workspace_lock(workspace): + console = Console(workspace) + server = make_server(console, args.port) + url = f"http://127.0.0.1:{server.server_port}/#token={console.token}" + print(f"HarborRL console: {url}\nWorkspace: {workspace}\nKeep this terminal open. Ctrl+C stops the console and requests active job cleanup.", flush=True) + if not args.no_browser: + try: + webbrowser.open(url) + except webbrowser.Error: + print("Browser unavailable; open the printed URL.", flush=True) + previous = signal.getsignal(signal.SIGTERM) + def terminate(_signum, _frame): + raise KeyboardInterrupt + signal.signal(signal.SIGTERM, terminate) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + console.jobs.close() + signal.signal(signal.SIGTERM, previous) + except (OSError, ValueError) as exc: + parser.error(str(exc)) + return 0 diff --git a/harborrl/webui/jobs.py b/harborrl/webui/jobs.py new file mode 100644 index 000000000..28a36df2b --- /dev/null +++ b/harborrl/webui/jobs.py @@ -0,0 +1,173 @@ +"""One foreground CLI job at a time, with immutable inputs and durable logs.""" +import json +import os +import signal +import subprocess +import threading +from datetime import datetime, timezone +from pathlib import Path +from uuid import uuid4 + +import yaml + +from harborrl.options.wizard import build_config_bundle, publish_bundle + + +def now(): + return datetime.now(timezone.utc).isoformat() + + +class Jobs: + def __init__(self, root): + self.root = Path(root) + self.root.mkdir(parents=True, exist_ok=True) + self.lock = threading.RLock() + self.child = None + self.active = None + self.closed = False + self.records = {} + for path in self.root.glob("*/job.json"): + record = json.loads(path.read_text(encoding="utf-8")) + if record["state"] in {"running", "stopping"}: + record.update(state="unknown", message="Console restarted; verify target cleanup before launching again.") + self.records[record["id"]] = record + + def _save(self, record): + path = self.root / record["id"] / "job.json" + temporary = path.with_suffix(".tmp") + temporary.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8") + temporary.replace(path) + + def _refresh(self): + if self.child is not None and self.child.poll() is not None: + record = self.records[self.active] + code = self.child.returncode + record.update(state="stopped" if record["state"] == "stopping" else "succeeded" if code == 0 else "failed", + exit_code=code, finished_at=now()) + self._save(record) + self.child = self.active = None + + def _watch(self, child): + child.wait() + with self.lock: + if self.child is child: + self._refresh() + + def list(self): + with self.lock: + self._refresh() + return [dict(record) for record in sorted(self.records.values(), key=lambda r: r["created_at"], reverse=True)] + + def start(self, configuration, bundle, mode): + if mode not in {"dry-run", "doctor", "train"}: + raise ValueError("Unknown run mode") + with self.lock: + self._refresh() + if self.closed or self.child is not None: + raise ValueError("A job is already active or the console is closing") + if any(record["state"] == "unknown" for record in self.records.values()): + raise ValueError("An interrupted job has unknown cleanup status. Reconcile it before launching again.") + identifier = uuid4().hex + root = self.root / identifier + root.mkdir(mode=0o700) + training = yaml.safe_load(bundle["files"]["config.yaml"]) + deployment = yaml.safe_load(bundle["files"]["deployment.yaml"]) + manifest = json.loads(bundle["files"]["setup-manifest.json"]) + output = str(Path(training["output"]["root"]) / "web" / identifier) + training["output"]["root"] = deployment["storage"]["run_root"] = output + snapshot = build_config_bundle({"answers_version": 1, "preset": manifest["preset"], + "training": training, "deployment": deployment}, output_directory=root / "inputs") + publish_bundle(snapshot, root / "inputs") + manager = deployment["manager"] + command = [manager["controller_python"], "-u", "-m", "harborrl.cli", "doctor" if mode == "doctor" else "train", + str(root / "inputs/config.yaml"), "--deployment", str(root / "inputs/deployment.yaml")] + if mode == "dry-run": + command.append("--dry-run") + record = {"id": identifier, "configuration": configuration, "mode": mode, "state": "running", + "created_at": now(), "finished_at": None, "exit_code": None, "command": command, + "log_path": str(root / "console.log"), "snapshot": str(root / "inputs"), "output_root": output} + self._save(record) + env = {**os.environ, "PYTHONUNBUFFERED": "1"} + env["PYTHONPATH"] = os.pathsep.join(filter(None, [manager["source_dir"], env.get("PYTHONPATH")])) + try: + with (root / "console.log").open("wb") as log: + self.child = subprocess.Popen(command, cwd=manager["source_dir"], env=env, stdin=subprocess.DEVNULL, + stdout=log, stderr=subprocess.STDOUT, start_new_session=True) + except OSError as exc: + record.update(state="failed", message=str(exc), finished_at=now()) + else: + self.active = identifier + record["pid"] = self.child.pid + self.records[identifier] = record + self._save(record) + if self.child is not None: + threading.Thread(target=self._watch, args=(self.child,), name="harborrl-web-job", daemon=True).start() + return dict(record) + + def stop(self, identifier): + with self.lock: + self._refresh() + if identifier != self.active or self.child is None: + raise ValueError("This job is not active") + record = self.records[identifier] + if record["state"] != "stopping": + # The existing CLI forwards signals to its local/SSH training target. + try: + self.child.send_signal(signal.SIGINT) if os.name == "posix" else self.child.terminate() + except ProcessLookupError: + self._refresh() + return dict(record) + record["state"] = "stopping" + self._save(record) + return dict(record) + + def reconcile(self, identifier): + with self.lock: + record = self.records[identifier] + if record["state"] != "unknown": + raise ValueError("Only an interrupted job can be reconciled") + record.update(state="interrupted", finished_at=now(), message="Target cleanup confirmed by operator.") + self._save(record) + return dict(record) + + def log(self, identifier): + with self.lock: + self._refresh() + record = self.records[identifier] + path = self.root / identifier / "console.log" + with path.open("rb") as stream: + size = stream.seek(0, os.SEEK_END) + stream.seek(max(0, size - 65536)) + tail = stream.read(65536).decode("utf-8", errors="replace") + result = {"job": dict(record), "text": tail, "bytes": size, "truncated": size > 65536, + "training_text": "", "training_log_path": None, "training_truncated": False} + output = Path(record["output_root"]) + candidates = sorted(output.glob("training/*/train-stdout.log")) + if candidates: + training_log = candidates[-1] + if training_log.resolve().is_relative_to(output.resolve()): + with training_log.open("rb") as stream: + length = stream.seek(0, os.SEEK_END) + stream.seek(max(0, length - 65536)) + result.update(training_text=stream.read(65536).decode("utf-8", errors="replace"), + training_log_path=str(training_log), training_truncated=length > 65536) + return result + + def close(self): + with self.lock: + self.closed = True + self._refresh() + child = self.child + if child: + self.stop(self.active) + if child: + try: + child.wait(timeout=30) + except subprocess.TimeoutExpired: + with self.lock: + record = self.records[self.active] + record.update(state="unknown", message="Graceful shutdown timed out; target cleanup must be checked.") + self._save(record) + else: + with self.lock: + self._refresh() diff --git a/harborrl/webui/server.py b/harborrl/webui/server.py new file mode 100644 index 000000000..442699175 --- /dev/null +++ b/harborrl/webui/server.py @@ -0,0 +1,189 @@ +"""Loopback-only HTTP boundary for the existing configuration services.""" +import hmac +import json +import os +import re +import secrets +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from importlib.resources import files +from pathlib import Path +from urllib.parse import urlsplit + +import yaml + +from harborrl.options.catalog import build_catalog +from harborrl.options.presets import PRESETS, defaults +from harborrl.options.validation import read_mapping, validate_mappings +from harborrl.options.wizard import build_config_bundle, publish_bundle + +from .jobs import Jobs + +ASSETS = {"/": ("index.html", "text/html; charset=utf-8"), + "/app.js": ("app.js", "text/javascript; charset=utf-8"), + "/style.css": ("style.css", "text/css; charset=utf-8"), + "/lucide.min.js": ("lucide.min.js", "text/javascript; charset=utf-8"), + "/brand.jpg": ("brand.jpg", "image/jpeg")} +NAME = re.compile(r"[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}") + + +class Console: + def __init__(self, workspace): + self.workspace = Path(workspace).expanduser().resolve() + self.configurations = self.workspace / "configurations" + self.configurations.mkdir(parents=True, exist_ok=True) + self.jobs = Jobs(self.workspace / "jobs") + self.token = secrets.token_urlsafe(32) + self.lock = threading.RLock() + + def directory(self, name): + if not isinstance(name, str) or not NAME.fullmatch(name): + raise ValueError("Name must be 1-64 letters, digits, underscores or hyphens") + path = self.configurations / name + if path.is_symlink() or path.resolve().parent != self.configurations: + raise ValueError("Configuration must stay inside the workspace") + return path + + def bootstrap(self): + return {"workspace": str(self.workspace), "catalog": build_catalog(), + "live_supported": os.name == "posix", + "defaults": {item["id"]: dict(zip(("training", "deployment"), + defaults(item["id"], self.configurations / "experiment"))) for item in PRESETS}, + "configurations": self.list_configurations(), "jobs": self.jobs.list()} + + def list_configurations(self): + result = [] + for path in sorted(self.configurations.iterdir()): + if path.is_dir() and not path.is_symlink() and NAME.fullmatch(path.name) and (path / "setup-manifest.json").is_file(): + result.append({"id": path.name, "path": str(path)}) + return result + + def preview(self, body): + output = self.directory(body["name"]) + bundle = build_config_bundle(body["answers"], output_directory=output) + if body.get("check_files") is True: + bundle["validation"] = validate_mappings(yaml.safe_load(bundle["files"]["config.yaml"]), + yaml.safe_load(bundle["files"]["deployment.yaml"]), config_path=output / "config.yaml", check_files=True) + return bundle + + def save(self, body): + with self.lock: + output = self.directory(body["name"]) + bundle = build_config_bundle(body["answers"], output_directory=output) + publish_bundle(bundle, output) + return {"id": output.name, "path": str(output), "validation": bundle["validation"]} + + def configuration(self, identifier): + root = self.directory(identifier) + manifest = json.loads((root / "setup-manifest.json").read_text(encoding="utf-8")) + answers = {"answers_version": 1, "preset": manifest["preset"], + "training": read_mapping(root / "config.yaml"), "deployment": read_mapping(root / "deployment.yaml")} + return {"id": identifier, "answers": answers, + "files": {name: (root / name).read_text(encoding="utf-8") for name in + ("config.yaml", "deployment.yaml", "setup-manifest.json")}} + + def start(self, body): + mode = body["mode"] + if mode not in {"dry-run", "doctor", "train"}: + raise ValueError("Unknown run mode") + if mode != "dry-run" and (body.get("confirmed") is not True or os.name != "posix"): + raise ValueError("Live jobs require explicit confirmation and a Linux/POSIX controller") + with self.lock: + identifier = body["configuration"] + root = self.directory(identifier) + config = self.configuration(identifier) + result = validate_mappings(yaml.safe_load(config["files"]["config.yaml"]), + yaml.safe_load(config["files"]["deployment.yaml"]), config_path=root / "config.yaml", check_files=True) + if result["status"] == "invalid": + raise ValueError("; ".join(row["message"] for row in result["checks"] if row["status"] == "failed")) + return self.jobs.start(identifier, config, mode) + + +def make_server(console, port=0): + class Handler(BaseHTTPRequestHandler): + def setup(self): + super().setup() + self.connection.settimeout(15) + + def log_message(self, *_args): + pass + + def respond(self, status, payload, content_type="application/json; charset=utf-8"): + data = payload if isinstance(payload, bytes) else json.dumps(payload).encode("utf-8") + self.send_response(status) + for name, value in {"Content-Type": content_type, "Content-Length": str(len(data)), + "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff", + "Referrer-Policy": "no-referrer", "X-Frame-Options": "DENY", + "Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'"}.items(): + self.send_header(name, value) + self.end_headers() + self.wfile.write(data) + + def dispatch(self, method): + try: + host = self.headers.get("Host", "") + if urlsplit("http://" + host).hostname not in {"127.0.0.1", "localhost"}: + return self.respond(403, {"error": "Loopback Host required"}) + path = urlsplit(self.path).path + if method == "GET" and path in ASSETS: + name, mime = ASSETS[path] + return self.respond(200, files("harborrl.webui").joinpath("static", name).read_bytes(), mime) + if not hmac.compare_digest(self.headers.get("Authorization", ""), "Bearer " + console.token): + return self.respond(403, {"error": "Console session expired or not authorized. Reopen the CLI URL."}) + if method == "GET": + if path == "/api/bootstrap": + return self.respond(200, console.bootstrap()) + if path == "/api/jobs": + return self.respond(200, console.jobs.list()) + if path.startswith("/api/jobs/"): + return self.respond(200, console.jobs.log(path.removeprefix("/api/jobs/"))) + if path.startswith("/api/configurations/"): + return self.respond(200, console.configuration(path.removeprefix("/api/configurations/"))) + if method == "POST": + if self.headers.get("Origin") != "http://" + host: + return self.respond(403, {"error": "Same-origin request required"}) + if self.headers.get("Content-Type", "").split(";")[0] != "application/json": + return self.respond(415, {"error": "JSON body required"}) + size = int(self.headers.get("Content-Length", "0")) + if not 0 < size <= 1048576: + return self.respond(413, {"error": "Request must be between 1 byte and 1 MiB"}) + body = json.loads(self.rfile.read(size)) + if not isinstance(body, dict): + raise TypeError("Request must be an object") + if path == "/api/preview": + return self.respond(200, console.preview(body)) + if path == "/api/configurations": + return self.respond(201, console.save(body)) + if path == "/api/jobs": + return self.respond(201, console.start(body)) + if path == "/api/stop": + return self.respond(200, console.jobs.stop(body["id"])) + if path == "/api/reconcile": + if body.get("confirmed") is not True: + raise ValueError("Confirm target cleanup before reconciling") + return self.respond(200, console.jobs.reconcile(body["id"])) + if path == "/api/import": + answers = yaml.safe_load(body["text"]) + if (not isinstance(answers, dict) or type(answers.get("answers_version")) is not int + or answers.get("answers_version") != 1): + raise ValueError("Import an answers_version: 1 YAML/JSON mapping") + if set(answers) - {"answers_version", "preset", "training", "deployment", "sandbox_config"}: + raise ValueError("Unknown answer fields") + if any(not isinstance(answers.get(key, {}), dict) for key in ("training", "deployment")): + raise ValueError("training and deployment answers must be objects") + if answers.get("preset") not in {p["id"] for p in PRESETS}: + raise ValueError("Unknown deployment preset") + return self.respond(200, {"answers": answers}) + self.respond(404, {"error": "Not found"}) + except (ValueError, TypeError, KeyError, OSError, AttributeError, yaml.YAMLError) as exc: + self.respond(400, {"error": str(exc)}) + + def do_GET(self): + self.dispatch("GET") + + def do_POST(self): + self.dispatch("POST") + + server = ThreadingHTTPServer(("127.0.0.1", port), Handler) + server.daemon_threads = True + return server diff --git a/harborrl/webui/static/app.js b/harborrl/webui/static/app.js new file mode 100644 index 000000000..0a6a5dc0d --- /dev/null +++ b/harborrl/webui/static/app.js @@ -0,0 +1,884 @@ +/* The form edits answers; Python remains the authoritative builder and validator. */ +"use strict"; +const $ = (selector) => document.querySelector(selector); +const $$ = (selector) => [...document.querySelectorAll(selector)]; +const escapeHtml = (value) => + String(value ?? "").replace( + /[&<>"']/g, + (c) => + ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ + c + ], + ); +const icon = (name) => ``; +const state = { + bootstrap: null, + answers: null, + step: 0, + saved: null, + preview: null, + file: "config.yaml", + view: "configuration", + selectedJob: null, + logSource: "console", + jobs: [], + busy: false, +}; +const fragment = new URLSearchParams(location.hash.slice(1)); +if (fragment.has("token")) { + sessionStorage.setItem("harborrl-token", fragment.get("token")); + history.replaceState(null, "", location.pathname); +} +const token = sessionStorage.getItem("harborrl-token") || ""; +function icons() { + lucide.createIcons({ attrs: { "aria-hidden": "true" } }); +} +function notify(text, success = false) { + $("#notice").textContent = text; + $("#notice").className = `notice${success ? " success" : ""}`; + $("#notice").hidden = false; +} +async function api(path, body) { + const response = await fetch(path, { + method: body === undefined ? "GET" : "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + ...(body === undefined ? {} : { body: JSON.stringify(body) }), + }); + const result = await response.json(); + if (!response.ok) throw new Error(result.error || "Request failed"); + return result; +} +async function action(callback) { + if (state.busy) return; + state.busy = true; + $("#notice").hidden = true; + try { + await callback(); + } catch (error) { + notify(error.message); + } finally { + state.busy = false; + updateActions(); + icons(); + } +} +function valueAt(object, path) { + return path.split(".").reduce((value, key) => value?.[key], object); +} +function setAt(object, path, value) { + const parts = path.split("."); + let parent = object; + parts.slice(0, -1).forEach((key) => { + if (!parent[key] || typeof parent[key] !== "object") parent[key] = {}; + parent = parent[key]; + }); + parent[parts.at(-1)] = value; +} +function defaultRoot() { + const separator = state.bootstrap.workspace.includes("\\") ? "\\" : "/"; + return [ + state.bootstrap.workspace, + "configurations", + $("#config-name").value, + "runs", + ].join(separator); +} +function read(path) { + const supplied = valueAt(state.answers, path); + if (supplied !== undefined) return supplied; + if (path === "sandbox_config") + return valueAt(state.answers, "training.harbor.environment.config"); + if (path === "deployment.storage.run_root") + return valueAt(state.answers, "training.output.root") ?? defaultRoot(); + if ( + [ + "training.deployment.actor_gpus", + "training.deployment.rollout_gpus", + ].includes(path) && + read("training.deployment.layout") === "colocate" + ) + return read("training.deployment.num_gpus"); + return valueAt(state.bootstrap.defaults[state.answers.preset], path); +} +function changed() { + state.saved = null; + state.preview = null; + $("#draft-state").textContent = "Draft"; + $("#draft-state").className = "badge"; + updateActions(); + renderSummary(); +} +function deriveWorkers() { + if (state.answers.preset === "service-ssh") { + delete state.answers.training.harbor?.workers; + delete state.answers.training.harbor?.python; + } +} +function newAnswers(preset = "embedded-local") { + return { answers_version: 1, preset, training: {}, deployment: {} }; +} +function field(path, label, type = "text", options = {}) { + const value = read(path), + id = path.replaceAll(".", "-"); + let control; + if (options.choices) + control = ``; + else if (type === "checkbox") + control = ``; + else + control = ``; + return `

${type === "checkbox" ? control : ""}${type === "checkbox" ? "" : control}${options.hint ? `${escapeHtml(options.hint)}` : ""}
`; +} +const section = (title, content) => + `

${title}

${content}
`; +function renderForm() { + const preset = state.answers.preset, + service = preset !== "embedded-local", + sandbox = preset === "service-sandbox-local-runner"; + let html = ""; + if (state.step === 0) { + const presets = [ + ["embedded-local", "Local", "One machine", "monitor"], + ["service-ssh", "SSH workers", "Distributed Docker", "network"], + ["service-sandbox-local-runner", "Sandbox", "Controller + GPU", "box"], + ]; + html = `

Deployment topology

${service ? "Service" : "Embedded"}
${presets.map(([id, label, detail, ico]) => ``).join("")}
`; + html += section( + "Controller", + field("deployment.manager.source_dir", "Source directory", "text", { + full: true, + hint: "Path on the controller", + required: true, + }) + + field( + "deployment.manager.controller_python", + "Controller Python", + "text", + { full: true, required: true }, + ) + + (service + ? field("deployment.manager.control_url", "Control origin", "url", { + placeholder: "http://controller.example:49200", + required: true, + }) + + field("deployment.manager.gateway_url", "Gateway origin", "url", { + placeholder: "http://controller.example:49184", + required: true, + }) + : ""), + ); + html += section( + "GPU target", + (service + ? field("deployment.gpu_target.ssh.host", "SSH destination", "text", { + placeholder: "gpu-training", + full: true, + required: true, + }) + : "") + + field("deployment.gpu_target.source_dir", "Source directory", "text", { + full: true, + required: true, + hint: "Path on the GPU machine", + }) + + field("deployment.gpu_target.train_python", "Training Python", "text", { + full: true, + required: true, + }), + ); + if (preset === "service-ssh") + html += `

Rollout workers

`; + else + html += section( + "Rollout runner", + field("training.harbor.python", "Harbor runner Python", "text", { + full: true, + required: true, + }) + + (sandbox + ? field("sandbox_config", "Sandbox configuration", "text", { + full: true, + placeholder: "/srv/site/sandbox.yaml", + required: true, + }) + : ""), + ); + html += section( + "Storage", + field( + "deployment.storage.run_root", + service ? "Shared run root" : "Run root", + "text", + { + full: true, + required: true, + hint: service + ? "Same absolute path on the controller, GPU and workers" + : "Path on this machine", + }, + ), + ); + } else if (state.step === 1) { + html += section( + "Model", + field("training.model.args_file", "Model args preset", "text", { + required: true, + }) + + field("training.harness.name", "Harness", "text", { + choices: state.bootstrap.catalog.harnesses.map((x) => x.id), + }) + + field("training.model.checkpoint", "Checkpoint directory", "text", { + full: true, + required: true, + hint: "Path on the GPU target", + }) + + field("training.model.reference", "Reference checkpoint", "text", { + full: true, + required: true, + }), + ); + html += section( + "Tasks & agent", + field("training.tasks.catalog", "Task catalog", "text", { + full: true, + required: true, + placeholder: "/srv/inputs/tasks.jsonl", + hint: "An existing locked JSON / JSONL catalog on the controller", + }) + + field("training.harness.profile", "Claude Code profile", "text", { + full: true, + required: true, + placeholder: "/srv/inputs/claude-code.json", + }), + ); + html += section( + "Gateway fingerprints", + field("training.gateway.tokenizer_digest", "Tokenizer SHA-256", "text", { + full: true, + required: true, + }) + + field("training.gateway.template_digest", "Template SHA-256", "text", { + full: true, + required: true, + }) + + (!service + ? field("training.gateway.host", "Bind address") + + field("training.gateway.advertised_url", "Advertised origin", "url") + : ""), + ); + } else if (state.step === 2) { + html += section( + "GPU allocation", + field("training.deployment.layout", "Layout", "text", { + choices: ["colocate", "split"], + }) + + field("training.deployment.num_gpus", "Total GPUs", "number") + + field("training.deployment.actor_gpus", "Actor GPUs", "number") + + field("training.deployment.rollout_gpus", "Serving GPUs", "number") + + field( + "training.deployment.actor_tensor_parallel_size", + "Actor tensor parallelism", + "number", + ) + + field( + "training.deployment.rollout_gpus_per_engine", + "Serving tensor parallelism", + "number", + ), + ); + html += section( + "Optimization", + field("training.training.num_rollout", "Rollout batches", "number") + + field("training.training.learning_rate", "Learning rate", "text") + + field("training.training.save_interval", "Save interval", "number") + + field( + "training.training.backend_contract", + "Algorithm contract", + "text", + { + choices: [ + ...new Set( + state.bootstrap.catalog.algorithms.flatMap( + (x) => x.training_backend_contracts, + ), + ), + ], + }, + ), + ); + html += section( + "Sampling", + field("training.sampling.group_size", "Samples per group", "number") + + field( + "training.sampling.groups_per_batch", + "Groups per batch", + "number", + ) + + field( + "training.sampling.max_tokens", + "Maximum output tokens", + "number", + ) + + field("training.sampling.max_context", "Context limit", "number") + + field("training.sampling.max_attempts", "Maximum attempts", "number") + + field( + "training.gateway.audited_raw_logprobs", + "Audited raw log probabilities", + "checkbox", + ), + ); + } + $("#form-sections").innerHTML = html; + $("#form-sections").hidden = state.step === 3; + $("#review").hidden = state.step !== 3; + if (state.step === 0 && preset === "service-ssh") renderWorkers(); + $$("[data-path]").forEach((input) => + input.addEventListener("change", () => { + const path = input.dataset.path; + let value = + input.type === "checkbox" + ? input.checked + : input.type === "number" || path.endsWith("learning_rate") + ? input.value === "" + ? null + : Number(input.value) + : input.value; + setAt(state.answers, path, value); + // Loaded bundles contain both sides of repeated schema fields. + const mirrors = { + "deployment.manager.control_url": null, + "deployment.manager.gateway_url": "training.gateway.advertised_url", + "deployment.storage.run_root": "training.output.root", + "training.gateway.host": "deployment.manager.bind", + "training.gateway.advertised_url": "deployment.manager.gateway_url", + }; + if (mirrors[path] && valueAt(state.answers, mirrors[path]) !== undefined) + setAt(state.answers, mirrors[path], value); + if ( + path === "sandbox_config" && + valueAt(state.answers, "training.harbor.environment.config") !== + undefined + ) + setAt(state.answers, "training.harbor.environment.config", value); + if ( + path.startsWith("deployment.rollout.workers.") && + state.answers.preset === "service-ssh" + ) { + deriveWorkers(); + } + if (path === "training.deployment.num_gpus") { + if ( + valueAt(state.answers, "deployment.gpu_target.expected_gpus") !== + undefined + ) + setAt(state.answers, "deployment.gpu_target.expected_gpus", value); + if (read("training.deployment.layout") === "colocate") + ["actor_gpus", "rollout_gpus"].forEach((key) => + setAt(state.answers, `training.deployment.${key}`, value), + ); + } + if (path === "training.deployment.layout" && value === "colocate") { + ["actor_gpus", "rollout_gpus"].forEach((key) => + setAt( + state.answers, + `training.deployment.${key}`, + read("training.deployment.num_gpus"), + ), + ); + } + changed(); + if (path.startsWith("training.deployment.")) renderForm(); + }), + ); + $$("[data-preset]").forEach((button) => + button.addEventListener("click", () => + action(async () => { + if (button.dataset.preset === state.answers.preset) return; + if ( + !(await confirmAction( + "Change deployment?", + "This starts a new draft for the selected topology. Saved configurations remain available.", + "Change topology", + )) + ) + return; + state.answers = newAnswers(button.dataset.preset); + changed(); + renderForm(); + }), + ), + ); + $("#add-worker")?.addEventListener("click", () => { + const workers = state.answers.deployment.rollout?.workers || []; + workers.push({ + id: `worker-${Date.now().toString(36)}`, + ssh: { host: "" }, + source_dir: "", + runner_python: "", + docker: true, + task_roots: [""], + }); + setAt(state.answers, "deployment.rollout.workers", workers); + deriveWorkers(); + changed(); + renderForm(); + }); + renderSummary(); + updateActions(); + icons(); +} +function renderWorkers() { + const workers = state.answers.deployment.rollout?.workers || []; + $("#worker-fields").innerHTML = workers.length + ? workers + .map( + (worker, index) => + `

Worker ${index + 1}

${field(`deployment.rollout.workers.${index}.id`, "ID")}${field(`deployment.rollout.workers.${index}.ssh.host`, "SSH destination")}${field(`deployment.rollout.workers.${index}.source_dir`, "Source directory", "text", { full: true })}${field(`deployment.rollout.workers.${index}.runner_python`, "Runner Python", "text", { full: true })}${field(`deployment.rollout.workers.${index}.task_roots.0`, "Task root", "text", { full: true })}
`, + ) + .join("") + : '

No workers configured.

'; + $$("[data-remove-worker]").forEach((button) => + button.addEventListener("click", () => { + workers.splice(Number(button.dataset.removeWorker), 1); + deriveWorkers(); + changed(); + renderForm(); + }), + ); +} +function renderSummary() { + const service = state.answers.preset !== "embedded-local", + sandbox = state.answers.preset === "service-sandbox-local-runner"; + const nodes = [ + ["monitor", "Controller", service ? "Service mode" : "Embedded mode"], + [ + "cpu", + "GPU target", + service + ? read("deployment.gpu_target.ssh.host") || "Not configured" + : "Local machine", + ], + [ + sandbox ? "box" : "network", + "Rollout", + sandbox + ? "Sandbox environment" + : service + ? `${state.answers.deployment.rollout?.workers?.length || 0} SSH workers` + : "Local Docker", + ], + ]; + $("#topology").innerHTML = nodes + .map( + ([ico, title, detail], index) => + `${index ? '
' : ""}
${icon(ico)}
${title}${escapeHtml(detail)}
`, + ) + .join(""); + const rows = [ + ["Model", read("training.model.args_file")], + ["Algorithm", "GRPO"], + ["GPUs", read("training.deployment.num_gpus")], + ["Layout", read("training.deployment.layout")], + [ + "Samples / batch", + Number(read("training.sampling.group_size")) * + Number(read("training.sampling.groups_per_batch")), + ], + ["Rollout batches", read("training.training.num_rollout")], + ]; + $("#resource-summary").innerHTML = + `
${rows.map(([key, value]) => `
${key}
${escapeHtml(value)}
`).join("")}
`; + $("#summary-status").textContent = state.saved + ? "Saved configuration" + : "Unsaved configuration"; + icons(); +} +function updateActions() { + $("#previous").disabled = state.step === 0 || state.busy; + $("#next").hidden = state.step === 3; + $("#save").hidden = state.step !== 3; + $("#next").disabled = state.busy; + $("#save").disabled = + state.busy || + !state.preview || + state.preview.validation.status === "invalid" || + !!state.saved; + $("#step-description").textContent = `Step ${state.step + 1} of 4`; + $$("[data-step]").forEach((button) => { + button.classList.toggle( + "active", + Number(button.dataset.step) === state.step, + ); + button.setAttribute( + "aria-current", + Number(button.dataset.step) === state.step ? "step" : "false", + ); + }); + const active = state.jobs.some((job) => + ["running", "stopping", "unknown"].includes(job.state), + ); + $("#dry-run").disabled = !state.saved || state.busy || active; + $("#doctor").disabled = + !state.saved || state.busy || active || !state.bootstrap?.live_supported; + $("#train").disabled = + !state.saved || state.busy || active || !state.bootstrap?.live_supported; +} +async function preview(checkFiles = false) { + state.preview = null; + $("#validation-result").textContent = "Checking configuration..."; + $("#config-preview").textContent = ""; + try { + state.preview = await api("/api/preview", { + name: $("#config-name").value, + answers: state.answers, + check_files: checkFiles, + }); + const result = state.preview.validation; + $("#validation-result").innerHTML = + `
${result.status === "invalid" ? "Changes required" : "Structure valid · runtime checks deferred"}
${result.checks.map((row) => `
${icon(row.status === "passed" ? "circle-check" : row.status === "failed" ? "circle-x" : "clock-3")}
${escapeHtml(row.path)}

${escapeHtml(row.message)}

`).join("")}`; + renderPreview(); + } catch (error) { + $("#validation-result").textContent = error.message; + throw error; + } finally { + updateActions(); + icons(); + } +} +function renderPreview() { + $("#config-preview").textContent = state.preview?.files[state.file] || ""; + $$("[data-file]").forEach((button) => + button.classList.toggle("active", button.dataset.file === state.file), + ); +} +async function goStep(step) { + state.step = step; + renderForm(); + if (step === 3) await preview(); +} +function showView(view) { + state.view = view; + $("#configuration-view").hidden = view !== "configuration"; + $("#runs-view").hidden = view !== "runs"; + $$("[data-view]").forEach((button) => + button.classList.toggle("active", button.dataset.view === view), + ); + if (view === "runs") renderJobs(); +} +function renderSaved() { + const configurations = state.bootstrap.configurations; + $("#saved-list").innerHTML = configurations.length + ? configurations + .map( + (config) => + ``, + ) + .join("") + : 'No configurations yet'; + $$("[data-config]").forEach((button) => + button.addEventListener("click", () => + action(async () => { + if ( + !state.saved && + !(await confirmAction( + "Open saved configuration?", + "The current draft will be replaced.", + "Open configuration", + )) + ) + return; + const config = await api( + `/api/configurations/${encodeURIComponent(button.dataset.config)}`, + ); + state.answers = config.answers; + state.saved = config.id; + $("#config-name").value = config.id; + $("#draft-state").textContent = "Saved"; + $("#draft-state").className = "badge saved"; + showView("configuration"); + await goStep(3); + renderSaved(); + }), + ), + ); + icons(); +} +function download(name, text, type = "text/plain") { + const url = URL.createObjectURL(new Blob([text], { type })); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = name; + anchor.click(); + setTimeout(() => URL.revokeObjectURL(url), 1000); +} +function confirmAction(title, message, label = "Confirm") { + const dialog = $("#confirm-dialog"); + $("#confirm-title").textContent = title; + $("#confirm-message").textContent = message; + $("#confirm-action").textContent = label; + dialog.returnValue = "cancel"; + return new Promise((resolve) => { + dialog.addEventListener( + "close", + () => resolve(dialog.returnValue === "confirm"), + { once: true }, + ); + dialog.showModal(); + }); +} +async function startJob(mode) { + if ( + mode !== "dry-run" && + !(await confirmAction( + mode === "train" ? "Start training?" : "Run live environment checks?", + mode === "train" + ? `Configuration: ${state.saved}\nGPU target: ${read("deployment.gpu_target.ssh.host") || "Local machine"}\nGPU budget: ${read("training.deployment.num_gpus")}\n\nTraining runs preflight, then allocates the configured GPU and rollout resources.` + : "Doctor executes environment and network checks on the configured machines.", + mode === "train" ? "Start training" : "Run doctor", + )) + ) + return; + const job = await api("/api/jobs", { + configuration: state.saved, + mode, + confirmed: mode !== "dry-run", + }); + state.selectedJob = job.id; + showView("runs"); + await refreshJobs(); +} +function renderJobs() { + $("#run-count").textContent = state.jobs.length; + $("#jobs-list").innerHTML = state.jobs.length + ? `
${state.jobs.map((job) => ``).join("")}
CONFIGURATIONMODESTARTEDSTATUSEXIT
${escapeHtml(job.configuration)}
${job.id.slice(0, 8)}
${escapeHtml(job.mode)}${new Date(job.created_at).toLocaleString()}${escapeHtml(job.state)}${job.exit_code ?? "—"}
` + : `
${icon("activity")}

No runs yet

Your dry-runs, environment checks and training jobs appear here.

`; + $$("[data-job]").forEach((row) => { + const select = () => + action(async () => { + state.selectedJob = row.dataset.job; + renderJobs(); + await loadLog(); + }); + row.addEventListener("click", select); + row.addEventListener("keydown", (event) => { + if (event.key === "Enter") select(); + }); + }); + icons(); +} +async function loadLog() { + if (!state.selectedJob) return; + const result = await api(`/api/jobs/${state.selectedJob}`), + job = result.job; + $("#job-detail").hidden = false; + $("#log-selector").hidden = false; + $("#job-title").textContent = `${job.configuration} · ${job.mode}`; + $("#job-meta").textContent = + `${job.state} · ${job.snapshot}${job.message ? ` · ${job.message}` : ""}`; + const log = $("#job-log"), + atBottom = log.scrollTop + log.clientHeight >= log.scrollHeight - 25; + const training = state.logSource === "training"; + log.textContent = + (training ? result.training_text : result.text) || + "Waiting for process output..."; + if (atBottom) log.scrollTop = log.scrollHeight; + $("#log-note").textContent = + `${(training ? result.training_truncated : result.truncated) ? "Showing the last 64 KiB. " : ""}${training ? result.training_log_path || "No separate GPU log yet" : job.log_path} · Output: ${job.output_root}`; + $("#stop-job").hidden = !["running", "stopping"].includes(job.state); + $("#stop-job").disabled = job.state === "stopping"; + $("#reconcile-job").hidden = job.state !== "unknown"; +} +async function refreshJobs() { + state.jobs = await api("/api/jobs"); + renderJobs(); + updateActions(); + if (state.view === "runs") await loadLog(); +} +async function importAnswers(text) { + const { answers } = await api("/api/import", { text }); + state.answers = answers; + changed(); + state.step = 0; + renderForm(); + showView("configuration"); + notify("Answers imported", true); +} +async function initialize() { + icons(); + try { + state.bootstrap = await api("/api/bootstrap"); + state.jobs = state.bootstrap.jobs; + state.answers = newAnswers(); + $("#workspace-path").textContent = state.bootstrap.workspace; + $("#connection-text").textContent = "Local session"; + renderForm(); + renderSaved(); + renderJobs(); + if (!state.bootstrap.live_supported) + notify( + "Configuration and dry-run are available here. Start this console on the Linux controller for live training.", + ); + } catch (error) { + $("#connection-text").textContent = "Disconnected"; + $(".connection").classList.add("offline"); + notify(error.message); + return; + } + $$("[data-view]").forEach((button) => + button.addEventListener("click", () => showView(button.dataset.view)), + ); + $$("[data-step]").forEach((button) => + button.addEventListener("click", () => + action(() => goStep(Number(button.dataset.step))), + ), + ); + $("#previous").addEventListener("click", () => + action(() => goStep(state.step - 1)), + ); + $("#next").addEventListener("click", () => + action(() => goStep(state.step + 1)), + ); + $("#config-name").addEventListener("change", () => { + changed(); + if (state.step === 3) action(() => preview()); + else renderForm(); + }); + $("#check-files").addEventListener("click", () => + action(() => preview(true)), + ); + $$("[data-file]").forEach((button) => + button.addEventListener("click", () => { + state.file = button.dataset.file; + renderPreview(); + }), + ); + $("#download-file").addEventListener("click", () => { + if (state.preview) download(state.file, state.preview.files[state.file]); + }); + $("#save").addEventListener("click", () => + action(async () => { + const saved = await api("/api/configurations", { + name: $("#config-name").value, + answers: state.answers, + }); + state.saved = saved.id; + state.bootstrap.configurations.push(saved); + $("#draft-state").textContent = "Saved"; + $("#draft-state").className = "badge saved"; + renderSaved(); + renderSummary(); + notify(`Saved ${saved.id}`, true); + }), + ); + ["dry-run", "doctor", "train"].forEach((mode) => + $("#" + mode).addEventListener("click", () => action(() => startJob(mode))), + ); + $("#new-config").addEventListener("click", () => + action(async () => { + if ( + !(await confirmAction( + "New configuration?", + "Start a fresh draft?", + "New configuration", + )) + ) + return; + state.answers = newAnswers(); + $("#config-name").value = + `experiment-${state.bootstrap.configurations.length + 1}`; + state.step = 0; + changed(); + showView("configuration"); + renderForm(); + renderSaved(); + }), + ); + $("#answers-button").addEventListener("click", () => { + $("#answers-error").hidden = true; + $("#answers-editor").value = JSON.stringify(state.answers, null, 2); + $("#answers-dialog").showModal(); + }); + $("#close-answers").addEventListener("click", () => + $("#answers-dialog").close(), + ); + $("#apply-answers").addEventListener("click", () => + action(async () => { + try { + await importAnswers($("#answers-editor").value); + $("#answers-dialog").close(); + } catch (error) { + $("#answers-error").textContent = error.message; + $("#answers-error").hidden = false; + } + }), + ); + $("#mobile-new").addEventListener("click", () => $("#new-config").click()); + $("#export-answers").addEventListener("click", () => + download("answers.json", $("#answers-editor").value, "application/json"), + ); + $("#import-button").addEventListener("click", () => + $("#import-file").click(), + ); + $("#import-file").addEventListener("change", () => + action(async () => { + const file = $("#import-file").files[0]; + if (file) { + if (file.size > 1048576) + throw new Error("Answers file must be at most 1 MiB"); + await importAnswers(await file.text()); + } + $("#import-file").value = ""; + }), + ); + $("#refresh-jobs").addEventListener("click", () => action(refreshJobs)); + $("#stop-job").addEventListener("click", () => + action(async () => { + if ( + await confirmAction( + "Stop this job?", + "Send a graceful stop request through the existing training CLI?", + "Stop job", + ) + ) { + await api("/api/stop", { id: state.selectedJob }); + await refreshJobs(); + } + }), + ); + $("#reconcile-job").addEventListener("click", () => + action(async () => { + if ( + await confirmAction( + "Confirm target cleanup?", + "Only continue after checking that the previous GPU and Sandbox processes have stopped. This releases the interrupted-job block.", + "Cleanup confirmed", + ) + ) { + await api("/api/reconcile", { id: state.selectedJob, confirmed: true }); + await refreshJobs(); + } + }), + ); + $("#copy-log").addEventListener("click", () => + download(`${state.selectedJob}.log`, $("#job-log").textContent), + ); + $("#log-source").addEventListener("change", () => { + state.logSource = $("#log-source").value; + action(loadLog); + }); + setInterval(async () => { + if (state.busy) return; + try { + await refreshJobs(); + $("#connection-text").textContent = "Local session"; + $(".connection").classList.remove("offline"); + } catch { + $("#connection-text").textContent = "Disconnected"; + $(".connection").classList.add("offline"); + } + }, 2500); +} +initialize(); diff --git a/harborrl/webui/static/brand.jpg b/harborrl/webui/static/brand.jpg new file mode 100644 index 000000000..e7cd9b29f Binary files /dev/null and b/harborrl/webui/static/brand.jpg differ diff --git a/harborrl/webui/static/index.html b/harborrl/webui/static/index.html new file mode 100644 index 000000000..bc76f9f58 --- /dev/null +++ b/harborrl/webui/static/index.html @@ -0,0 +1,273 @@ + + + + + + HarborRL · Training console + + + + + + +
+ HarborRL + Training console +
+ Connecting +
+
+
+ +
+ +
+
+
+
EXPERIMENT SETUP
+

Training configuration

+
+
+ + +
+
+
+ Draft +
+ +
+
+
+ + +
+ +
+
+ +
+
+ +
+
+

Confirm

+ +
+

+
+ +
+
+
+ +
+

Advanced answers

+ +
+ + +
+ +
+
+ + diff --git a/harborrl/webui/static/lucide-LICENSE.txt b/harborrl/webui/static/lucide-LICENSE.txt new file mode 100644 index 000000000..718bb3f0e --- /dev/null +++ b/harborrl/webui/static/lucide-LICENSE.txt @@ -0,0 +1,43 @@ +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +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. diff --git a/harborrl/webui/static/lucide.min.js b/harborrl/webui/static/lucide.min.js new file mode 100644 index 000000000..e3e5586e9 --- /dev/null +++ b/harborrl/webui/static/lucide.min.js @@ -0,0 +1,12 @@ +/** + * @license lucide v1.61.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +(function(a,i){typeof exports=="object"&&typeof module<"u"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(a=typeof globalThis<"u"?globalThis:a||self,i(a.lucide={}))})(this,(function(a){"use strict";const i={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"},Ka=([h,t,c])=>{const d=document.createElementNS("http://www.w3.org/2000/svg",h);return Object.keys(t).forEach(M=>{d.setAttribute(M,String(t[M]))}),c?.length&&c.forEach(M=>{const p=Ka(M);d.appendChild(p)}),d},Ja=(h,t={})=>{const c={...i,...t};return Ka(["svg",c,h])},FS=(...h)=>h.filter((t,c,d)=>!!t&&t.trim()!==""&&d.indexOf(t)===c).join(" ").trim(),RS=h=>{for(const t in h)if(t.startsWith("aria-")||t==="role"||t==="title")return!0;return!1},bS=h=>{let t="",c=!1;for(const d of h){if(d==="-"||d==="_"||d<=" "){c=t.length>0;continue}t.length===0?t+=d.toLowerCase():t+=c?d.toUpperCase():d,c=!1}return t},TS=h=>{const t=bS(h);return t.charAt(0).toUpperCase()+t.slice(1)},qS=h=>Array.from(h.attributes).reduce((t,c)=>(t[c.name]=c.value,t),{}),Qa=h=>typeof h=="string"?h:!h||!h.class?"":h.class&&typeof h.class=="string"?h.class.split(" "):h.class&&Array.isArray(h.class)?h.class:"",ja=(h,{nameAttr:t,icons:c,attrs:d})=>{const M=h.getAttribute(t);if(M==null)return;const p=TS(M),Xa=c[p];if(!Xa)return console.warn(`${h.outerHTML} icon name was not found in the provided icons object.`);const Na=qS(h),US=RS(Na)?{}:{"aria-hidden":"true"},zS={...i,"data-lucide":M,...US,...d,...Na},OS=Qa(Na),GS=Qa(d),DS=FS("lucide",`lucide-${M}`,...OS,...GS);DS&&Object.assign(zS,{class:DS});const ZS=Ja(Xa,zS);return h.parentNode?.replaceChild(ZS,h)},Ya=[["path",{d:"m14 12 4 4 4-4"}],["path",{d:"M18 16V7"}],["path",{d:"m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"}],["path",{d:"M3.304 13h6.392"}]],_a=[["path",{d:"m14 11 4-4 4 4"}],["path",{d:"M18 16V7"}],["path",{d:"m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"}],["path",{d:"M3.304 13h6.392"}]],xa=[["path",{d:"m15 16 2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16"}],["path",{d:"M15.697 14h5.606"}],["path",{d:"m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"}],["path",{d:"M3.304 13h6.392"}]],ah=[["circle",{cx:"16",cy:"4",r:"1"}],["path",{d:"m18 19 1-7-6 1"}],["path",{d:"m5 8 3-3 5.5 3-2.36 3.5"}],["path",{d:"M4.24 14.5a5 5 0 0 0 6.88 6"}],["path",{d:"M13.76 17.5a5 5 0 0 0-6.88-6"}]],hh=[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"}]],th=[["path",{d:"M10 13H6"}],["path",{d:"M10 15v-4a2 2 0 0 0-4 0v4"}],["path",{d:"M14 14.5a.5.5 0 0 0 .5.5h1a2.5 2.5 0 0 0 2.5-2.5v-1A2.5 2.5 0 0 0 15.5 9h-1a.5.5 0 0 0-.5.5z"}],["rect",{x:"2",y:"5",width:"20",height:"14",rx:"2"}]],dh=[["path",{d:"M18 17.5a2.5 2.5 0 1 1-4 2.03V12"}],["path",{d:"M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"}],["path",{d:"M6 8h12"}],["path",{d:"M6.6 15.572A2 2 0 1 0 10 17v-5"}]],ch=[["path",{d:"M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1"}],["path",{d:"m12 15 5 6H7Z"}]],C=[["circle",{cx:"12",cy:"13",r:"8"}],["path",{d:"M5 3 2 6"}],["path",{d:"m22 6-3-3"}],["path",{d:"M6.38 18.7 4 21"}],["path",{d:"M17.64 18.67 20 21"}],["path",{d:"m9 13 2 2 4-4"}]],u=[["circle",{cx:"12",cy:"13",r:"8"}],["path",{d:"M5 3 2 6"}],["path",{d:"m22 6-3-3"}],["path",{d:"M6.38 18.7 4 21"}],["path",{d:"M17.64 18.67 20 21"}],["path",{d:"M9 13h6"}]],Mh=[["path",{d:"M6.87 6.87a8 8 0 1 0 11.26 11.26"}],["path",{d:"M19.9 14.25a8 8 0 0 0-9.15-9.15"}],["path",{d:"m22 6-3-3"}],["path",{d:"M6.26 18.67 4 21"}],["path",{d:"m2 2 20 20"}],["path",{d:"M4 4 2 6"}]],g=[["circle",{cx:"12",cy:"13",r:"8"}],["path",{d:"M5 3 2 6"}],["path",{d:"m22 6-3-3"}],["path",{d:"M6.38 18.7 4 21"}],["path",{d:"M17.64 18.67 20 21"}],["path",{d:"M12 10v6"}],["path",{d:"M9 13h6"}]],ph=[["circle",{cx:"12",cy:"13",r:"8"}],["path",{d:"M12 9v4l2 2"}],["path",{d:"M5 3 2 6"}],["path",{d:"m22 6-3-3"}],["path",{d:"M6.38 18.7 4 21"}],["path",{d:"M17.64 18.67 20 21"}]],lh=[["path",{d:"M11 21c0-2.5 2-2.5 2-5"}],["path",{d:"M16 21c0-2.5 2-2.5 2-5"}],["path",{d:"m19 8-.8 3a1.25 1.25 0 0 1-1.2 1H7a1.25 1.25 0 0 1-1.2-1L5 8"}],["path",{d:"M21 3a1 1 0 0 1 1 1v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a1 1 0 0 1 1-1z"}],["path",{d:"M6 21c0-2.5 2-2.5 2-5"}]],ih=[["path",{d:"M2 12h20"}],["path",{d:"M10 16v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-4"}],["path",{d:"M10 8V4a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v4"}],["path",{d:"M20 16v1a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-1"}],["path",{d:"M14 8V7c0-1.1.9-2 2-2h2a2 2 0 0 1 2 2v1"}]],nh=[["path",{d:"M12 2v20"}],["path",{d:"M8 10H4a2 2 0 0 1-2-2V6c0-1.1.9-2 2-2h4"}],["path",{d:"M16 10h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-4"}],["path",{d:"M8 20H7a2 2 0 0 1-2-2v-2c0-1.1.9-2 2-2h1"}],["path",{d:"M16 14h1a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2h-1"}]],eh=[["rect",{width:"6",height:"16",x:"4",y:"2",rx:"2"}],["rect",{width:"6",height:"9",x:"14",y:"9",rx:"2"}],["path",{d:"M22 22H2"}]],rh=[["rect",{width:"16",height:"6",x:"2",y:"4",rx:"2"}],["rect",{width:"9",height:"6",x:"9",y:"14",rx:"2"}],["path",{d:"M22 22V2"}]],oh=[["rect",{width:"6",height:"14",x:"4",y:"5",rx:"2"}],["rect",{width:"6",height:"10",x:"14",y:"7",rx:"2"}],["path",{d:"M17 22v-5"}],["path",{d:"M17 7V2"}],["path",{d:"M7 22v-3"}],["path",{d:"M7 5V2"}]],vh=[["rect",{width:"6",height:"14",x:"4",y:"5",rx:"2"}],["rect",{width:"6",height:"10",x:"14",y:"7",rx:"2"}],["path",{d:"M10 2v20"}],["path",{d:"M20 2v20"}]],$h=[["rect",{width:"6",height:"14",x:"4",y:"5",rx:"2"}],["rect",{width:"6",height:"10",x:"14",y:"7",rx:"2"}],["path",{d:"M4 2v20"}],["path",{d:"M14 2v20"}]],mh=[["rect",{width:"6",height:"14",x:"2",y:"5",rx:"2"}],["rect",{width:"6",height:"10",x:"16",y:"7",rx:"2"}],["path",{d:"M12 2v20"}]],yh=[["rect",{width:"6",height:"14",x:"2",y:"5",rx:"2"}],["rect",{width:"6",height:"10",x:"12",y:"7",rx:"2"}],["path",{d:"M22 2v20"}]],sh=[["rect",{width:"6",height:"14",x:"6",y:"5",rx:"2"}],["rect",{width:"6",height:"10",x:"16",y:"7",rx:"2"}],["path",{d:"M2 2v20"}]],Ch=[["rect",{width:"6",height:"10",x:"9",y:"7",rx:"2"}],["path",{d:"M4 22V2"}],["path",{d:"M20 22V2"}]],uh=[["rect",{width:"6",height:"14",x:"3",y:"5",rx:"2"}],["rect",{width:"6",height:"10",x:"15",y:"7",rx:"2"}],["path",{d:"M3 2v20"}],["path",{d:"M21 2v20"}]],gh=[["rect",{width:"6",height:"16",x:"4",y:"6",rx:"2"}],["rect",{width:"6",height:"9",x:"14",y:"6",rx:"2"}],["path",{d:"M22 2H2"}]],Ah=[["path",{d:"M22 17h-3"}],["path",{d:"M22 7h-5"}],["path",{d:"M5 17H2"}],["path",{d:"M7 7H2"}],["rect",{x:"5",y:"14",width:"14",height:"6",rx:"2"}],["rect",{x:"7",y:"4",width:"10",height:"6",rx:"2"}]],Hh=[["rect",{width:"9",height:"6",x:"6",y:"14",rx:"2"}],["rect",{width:"16",height:"6",x:"6",y:"4",rx:"2"}],["path",{d:"M2 2v20"}]],Vh=[["rect",{width:"14",height:"6",x:"5",y:"14",rx:"2"}],["rect",{width:"10",height:"6",x:"7",y:"4",rx:"2"}],["path",{d:"M2 20h20"}],["path",{d:"M2 10h20"}]],Sh=[["rect",{width:"14",height:"6",x:"5",y:"14",rx:"2"}],["rect",{width:"10",height:"6",x:"7",y:"4",rx:"2"}],["path",{d:"M2 14h20"}],["path",{d:"M2 4h20"}]],wh=[["rect",{width:"14",height:"6",x:"5",y:"16",rx:"2"}],["rect",{width:"10",height:"6",x:"7",y:"2",rx:"2"}],["path",{d:"M2 12h20"}]],Lh=[["rect",{width:"14",height:"6",x:"5",y:"12",rx:"2"}],["rect",{width:"10",height:"6",x:"7",y:"2",rx:"2"}],["path",{d:"M2 22h20"}]],fh=[["rect",{width:"10",height:"6",x:"7",y:"9",rx:"2"}],["path",{d:"M22 20H2"}],["path",{d:"M22 4H2"}]],kh=[["rect",{width:"14",height:"6",x:"5",y:"16",rx:"2"}],["rect",{width:"10",height:"6",x:"7",y:"6",rx:"2"}],["path",{d:"M2 2h20"}]],Ph=[["rect",{width:"14",height:"6",x:"5",y:"15",rx:"2"}],["rect",{width:"10",height:"6",x:"7",y:"3",rx:"2"}],["path",{d:"M2 21h20"}],["path",{d:"M2 3h20"}]],Bh=[["path",{d:"M10 10H6"}],["path",{d:"M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"}],["path",{d:"M19 18h2a1 1 0 0 0 1-1v-3.28a1 1 0 0 0-.684-.948l-1.923-.641a1 1 0 0 1-.578-.502l-1.539-3.076A1 1 0 0 0 16.382 8H14"}],["path",{d:"M8 8v4"}],["path",{d:"M9 18h6"}],["circle",{cx:"17",cy:"18",r:"2"}],["circle",{cx:"7",cy:"18",r:"2"}]],zh=[["path",{d:"M16 12h3"}],["path",{d:"M17.5 12a8 8 0 0 1-8 8A4.5 4.5 0 0 1 5 15.5c0-6 8-4 8-8.5a3 3 0 1 0-6 0c0 3 2.5 8.5 12 13"}]],Dh=[["path",{d:"M10 17c-5-3-7-7-7-9a2 2 0 0 1 4 0c0 2.5-5 2.5-5 6 0 1.7 1.3 3 3 3 2.8 0 5-2.2 5-5"}],["path",{d:"M22 17c-5-3-7-7-7-9a2 2 0 0 1 4 0c0 2.5-5 2.5-5 6 0 1.7 1.3 3 3 3 2.8 0 5-2.2 5-5"}]],Fh=[["path",{d:"M10 2v5.632c0 .424-.272.795-.653.982A6 6 0 0 0 6 14c.006 4 3 7 5 8"}],["path",{d:"M10 5H8a2 2 0 0 0 0 4h.68"}],["path",{d:"M14 2v5.632c0 .424.272.795.652.982A6 6 0 0 1 18 14c0 4-3 7-5 8"}],["path",{d:"M14 5h2a2 2 0 0 1 0 4h-.68"}],["path",{d:"M18 22H6"}],["path",{d:"M9 2h6"}]],Rh=[["path",{d:"M12 6v16"}],["path",{d:"m19 13 2-1a9 9 0 0 1-18 0l2 1"}],["path",{d:"M9 11h6"}],["circle",{cx:"12",cy:"4",r:"2"}]],bh=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M3 11a10 10 0 0 1 10 10"}]],Th=[["path",{d:"M2 12 7 2"}],["path",{d:"m7 12 5-10"}],["path",{d:"m12 12 5-10"}],["path",{d:"m17 12 5-10"}],["path",{d:"M4.5 7h15"}],["path",{d:"M12 16v6"}]],qh=[["path",{d:"M7 10H6a4 4 0 0 1-4-4 1 1 0 0 1 1-1h4"}],["path",{d:"M7 5a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1 7 7 0 0 1-7 7H8a1 1 0 0 1-1-1z"}],["path",{d:"M9 12v5"}],["path",{d:"M15 12v5"}],["path",{d:"M5 20a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3 1 1 0 0 1-1 1H6a1 1 0 0 1-1-1"}]],Uh=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m14.31 8 5.74 9.94"}],["path",{d:"M9.69 8h11.48"}],["path",{d:"m7.38 12 5.74-9.94"}],["path",{d:"M9.69 16 3.95 6.06"}],["path",{d:"M14.31 16H2.83"}],["path",{d:"m16.62 12-5.74 9.94"}]],Oh=[["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2"}],["path",{d:"M6 8h.01"}],["path",{d:"M10 8h.01"}],["path",{d:"M14 8h.01"}]],Gh=[["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2"}],["path",{d:"M10 4v4"}],["path",{d:"M2 8h20"}],["path",{d:"M6 4v4"}]],Zh=[["path",{d:"M12 6.528V3a1 1 0 0 1 1-1h0"}],["path",{d:"M18.237 21A15 15 0 0 0 22 11a6 6 0 0 0-10-4.472A6 6 0 0 0 2 11a15.1 15.1 0 0 0 3.763 10 3 3 0 0 0 3.648.648 5.5 5.5 0 0 1 5.178 0A3 3 0 0 0 18.237 21"}]],Eh=[["rect",{width:"20",height:"5",x:"2",y:"3",rx:"1"}],["path",{d:"M4 8v11a2 2 0 0 0 2 2h2"}],["path",{d:"M20 8v11a2 2 0 0 1-2 2h-2"}],["path",{d:"m9 15 3-3 3 3"}],["path",{d:"M12 12v9"}]],Wh=[["rect",{width:"20",height:"5",x:"2",y:"3",rx:"1"}],["path",{d:"M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"}],["path",{d:"M10 12h4"}]],Ih=[["path",{d:"M19 9V6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v3"}],["path",{d:"M3 16a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z"}],["path",{d:"M5 18v2"}],["path",{d:"M19 18v2"}]],Xh=[["rect",{width:"20",height:"5",x:"2",y:"3",rx:"1"}],["path",{d:"M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"}],["path",{d:"m9.5 17 5-5"}],["path",{d:"m9.5 12 5 5"}]],Nh=[["path",{d:"M11 10h8"}],["path",{d:"M11 14h8"}],["path",{d:"M17 20V10a6 6 0 0 0-12 0"}]],Kh=[["path",{d:"M14 8a1 1 0 0 1 1 1v2a1 1 0 0 0 1 1h3.293a.707.707 0 0 1 .5 1.207l-6.939 6.939a1.207 1.207 0 0 1-1.708 0l-6.94-6.94a.707.707 0 0 1 .5-1.206H8a1 1 0 0 0 1-1V9a1 1 0 0 1 1-1z"}],["path",{d:"M9 4h6"}]],Jh=[["path",{d:"M9 5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v6a1 1 0 0 0 1 1h3.293a.707.707 0 0 1 .5 1.207l-7.086 7.086a1 1 0 0 1-1.414 0l-7.086-7.086a.707.707 0 0 1 .5-1.207H8a1 1 0 0 0 1-1z"}]],Qh=[["path",{d:"M13 9a1 1 0 0 1-1-1V4.707a.707.707 0 0 0-1.207-.5l-6.94 6.94a1.207 1.207 0 0 0 0 1.707l6.94 6.94a.707.707 0 0 0 1.207-.5V16a1 1 0 0 1 1-1h2a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1z"}],["path",{d:"M20 9v6"}]],jh=[["path",{d:"M10.793 19.793a.707.707 0 0 0 1.207-.5V16a1 1 0 0 1 1-1h6a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1h-6a1 1 0 0 1-1-1V4.707a.707.707 0 0 0-1.207-.5l-6.94 6.94a1.207 1.207 0 0 0 0 1.707z"}]],Yh=[["path",{d:"M11 9a1 1 0 0 0 1-1V4.707a.707.707 0 0 1 1.207-.5l6.94 6.94a1.207 1.207 0 0 1 0 1.707l-6.94 6.94a.707.707 0 0 1-1.207-.5V16a1 1 0 0 0-1-1H9a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1z"}],["path",{d:"M4 9v6"}]],_h=[["path",{d:"M13.207 19.793a.707.707 0 0 1-1.207-.5V16a1 1 0 0 0-1-1H5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h6a1 1 0 0 0 1-1V4.707a.707.707 0 0 1 1.207-.5l6.94 6.94a1.207 1.207 0 0 1 0 1.707z"}]],xh=[["path",{d:"M14 16a1 1 0 0 0 1-1v-2a1 1 0 0 1 1-1h3.293a.707.707 0 0 0 .5-1.207l-6.939-6.939a1.207 1.207 0 0 0-1.708 0l-6.94 6.94a.707.707 0 0 0 .5 1.206H8a1 1 0 0 1 1 1v2a1 1 0 0 0 1 1z"}],["path",{d:"M9 20h6"}]],at=[["path",{d:"M9 19a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-6a1 1 0 0 1 1-1h3.293a.707.707 0 0 0 .5-1.207l-7.086-7.086a1 1 0 0 0-1.414 0l-7.086 7.086a.707.707 0 0 0 .5 1.207H8a1 1 0 0 1 1 1z"}]],ht=[["path",{d:"m3 16 4 4 4-4"}],["path",{d:"M7 20V4"}],["rect",{x:"15",y:"4",width:"4",height:"6",ry:"2"}],["path",{d:"M17 20v-6h-2"}],["path",{d:"M15 20h4"}]],tt=[["path",{d:"m3 16 4 4 4-4"}],["path",{d:"M7 20V4"}],["path",{d:"M17 10V4h-2"}],["path",{d:"M15 10h4"}],["rect",{x:"15",y:"14",width:"4",height:"6",ry:"2"}]],A=[["path",{d:"m3 16 4 4 4-4"}],["path",{d:"M7 20V4"}],["path",{d:"M20 8h-5"}],["path",{d:"M15 10V6.5a2.5 2.5 0 0 1 5 0V10"}],["path",{d:"M15 14h5l-5 6h5"}]],dt=[["path",{d:"M19 3H5"}],["path",{d:"M12 21V7"}],["path",{d:"m6 15 6 6 6-6"}]],ct=[["path",{d:"M17 7 7 17"}],["path",{d:"M17 17H7V7"}]],Mt=[["path",{d:"m3 16 4 4 4-4"}],["path",{d:"M7 20V4"}],["path",{d:"M11 4h4"}],["path",{d:"M11 8h7"}],["path",{d:"M11 12h10"}]],pt=[["path",{d:"m7 7 10 10"}],["path",{d:"M17 7v10H7"}]],lt=[["path",{d:"M12 2v14"}],["path",{d:"m19 9-7 7-7-7"}],["circle",{cx:"12",cy:"21",r:"1"}]],it=[["path",{d:"M12 17V3"}],["path",{d:"m6 11 6 6 6-6"}],["path",{d:"M19 21H5"}]],nt=[["path",{d:"m3 16 4 4 4-4"}],["path",{d:"M7 20V4"}],["path",{d:"m21 8-4-4-4 4"}],["path",{d:"M17 4v16"}]],H=[["path",{d:"m3 16 4 4 4-4"}],["path",{d:"M7 20V4"}],["path",{d:"M11 4h10"}],["path",{d:"M11 8h7"}],["path",{d:"M11 12h4"}]],V=[["path",{d:"m3 16 4 4 4-4"}],["path",{d:"M7 4v16"}],["path",{d:"M15 4h5l-5 6h5"}],["path",{d:"M15 20v-3.5a2.5 2.5 0 0 1 5 0V20"}],["path",{d:"M20 18h-5"}]],et=[["path",{d:"M12 5v14"}],["path",{d:"m19 12-7 7-7-7"}]],rt=[["path",{d:"m9 6-6 6 6 6"}],["path",{d:"M3 12h14"}],["path",{d:"M21 19V5"}]],ot=[["path",{d:"M3 19V5"}],["path",{d:"m13 6-6 6 6 6"}],["path",{d:"M7 12h14"}]],vt=[["path",{d:"M8 3 4 7l4 4"}],["path",{d:"M4 7h16"}],["path",{d:"m16 21 4-4-4-4"}],["path",{d:"M20 17H4"}]],$t=[["path",{d:"m12 19-7-7 7-7"}],["path",{d:"M19 12H5"}]],mt=[["path",{d:"M3 5v14"}],["path",{d:"M21 12H7"}],["path",{d:"m15 18 6-6-6-6"}]],yt=[["path",{d:"m16 3 4 4-4 4"}],["path",{d:"M20 7H4"}],["path",{d:"m8 21-4-4 4-4"}],["path",{d:"M4 17h16"}]],st=[["path",{d:"M17 12H3"}],["path",{d:"m11 18 6-6-6-6"}],["path",{d:"M21 5v14"}]],Ct=[["path",{d:"M5 12h14"}],["path",{d:"m12 5 7 7-7 7"}]],ut=[["path",{d:"m3 8 4-4 4 4"}],["path",{d:"M7 4v16"}],["path",{d:"M17 10V4h-2"}],["path",{d:"M15 10h4"}],["rect",{x:"15",y:"14",width:"4",height:"6",ry:"2"}]],gt=[["path",{d:"m3 8 4-4 4 4"}],["path",{d:"M7 4v16"}],["rect",{x:"15",y:"4",width:"4",height:"6",ry:"2"}],["path",{d:"M17 20v-6h-2"}],["path",{d:"M15 20h4"}]],S=[["path",{d:"m3 8 4-4 4 4"}],["path",{d:"M7 4v16"}],["path",{d:"M20 8h-5"}],["path",{d:"M15 10V6.5a2.5 2.5 0 0 1 5 0V10"}],["path",{d:"M15 14h5l-5 6h5"}]],At=[["path",{d:"m21 16-4 4-4-4"}],["path",{d:"M17 20V4"}],["path",{d:"m3 8 4-4 4 4"}],["path",{d:"M7 4v16"}]],Ht=[["path",{d:"m5 9 7-7 7 7"}],["path",{d:"M12 16V2"}],["circle",{cx:"12",cy:"21",r:"1"}]],Vt=[["path",{d:"m18 9-6-6-6 6"}],["path",{d:"M12 3v14"}],["path",{d:"M5 21h14"}]],St=[["path",{d:"M7 17V7h10"}],["path",{d:"M17 17 7 7"}]],w=[["path",{d:"m3 8 4-4 4 4"}],["path",{d:"M7 4v16"}],["path",{d:"M11 12h4"}],["path",{d:"M11 16h7"}],["path",{d:"M11 20h10"}]],wt=[["path",{d:"M7 7h10v10"}],["path",{d:"M7 17 17 7"}]],Lt=[["path",{d:"M5 3h14"}],["path",{d:"m18 13-6-6-6 6"}],["path",{d:"M12 7v14"}]],ft=[["path",{d:"m3 8 4-4 4 4"}],["path",{d:"M7 4v16"}],["path",{d:"M11 12h10"}],["path",{d:"M11 16h7"}],["path",{d:"M11 20h4"}]],L=[["path",{d:"m3 8 4-4 4 4"}],["path",{d:"M7 4v16"}],["path",{d:"M15 4h5l-5 6h5"}],["path",{d:"M15 20v-3.5a2.5 2.5 0 0 1 5 0V20"}],["path",{d:"M20 18h-5"}]],kt=[["path",{d:"m5 12 7-7 7 7"}],["path",{d:"M12 19V5"}]],Pt=[["path",{d:"m4 6 3-3 3 3"}],["path",{d:"M7 17V3"}],["path",{d:"m14 6 3-3 3 3"}],["path",{d:"M17 17V3"}],["path",{d:"M4 21h16"}]],Bt=[["path",{d:"M12.983 21.186a1 1 0 0 1-1.966 0 10 10 0 0 0-8.203-8.203 1 1 0 0 1 0-1.966 10 10 0 0 0 8.203-8.203 1 1 0 0 1 1.966 0 10 10 0 0 0 8.203 8.203 1 1 0 0 1 0 1.966 10 10 0 0 0-8.203 8.203"}]],zt=[["path",{d:"M12 5v14"}],["path",{d:"m18.065 8.496-12.125 7"}],["path",{d:"m5.94 8.504 12.125 7"}]],Dt=[["circle",{cx:"12",cy:"12",r:"4"}],["path",{d:"M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"}]],Ft=[["circle",{cx:"12",cy:"12",r:"1"}],["path",{d:"M20.2 20.2c2.04-2.03.02-7.36-4.5-11.9-4.54-4.52-9.87-6.54-11.9-4.5-2.04 2.03-.02 7.36 4.5 11.9 4.54 4.52 9.87 6.54 11.9 4.5Z"}],["path",{d:"M15.7 15.7c4.52-4.54 6.54-9.87 4.5-11.9-2.03-2.04-7.36-.02-11.9 4.5-4.52 4.54-6.54 9.87-4.5 11.9 2.03 2.04 7.36.02 11.9-4.5Z"}]],Rt=[["path",{d:"M10 10v11"}],["path",{d:"M10 3v1.35"}],["path",{d:"M14 14v1"}],["path",{d:"M14 8v.35"}],["path",{d:"M18 5v7.35"}],["path",{d:"M2 10v3"}],["path",{d:"m2 2 20 20"}],["path",{d:"M22 10v3"}],["path",{d:"M6 6v11"}]],bt=[["path",{d:"M10 3v18"}],["path",{d:"M14 8v6.35"}],["path",{d:"m17 17 5 5"}],["path",{d:"M18 5v8.1"}],["path",{d:"M2 10v3"}],["path",{d:"M22 10v3"}],["path",{d:"m22 17-5 5"}],["path",{d:"M6 6v11"}]],Tt=[["path",{d:"M2 10v3"}],["path",{d:"M6 6v11"}],["path",{d:"M10 3v18"}],["path",{d:"M14 8v7"}],["path",{d:"M18 5v13"}],["path",{d:"M22 10v3"}]],qt=[["path",{d:"M2 13a2 2 0 0 0 2-2V7a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0V4a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0v-4a2 2 0 0 1 2-2"}]],Ut=[["path",{d:"M13.465 3.464a5 5 0 017.07 0 5 5 0 010 7.072c-1.204 1.203-2.38 2.89-2.614 4.577A8 8 0 118.886 6.08c1.687-.235 3.375-1.41 4.579-2.615"}],["circle",{cx:"10",cy:"14",r:"3"}]],Ot=[["path",{d:"m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526"}],["circle",{cx:"12",cy:"8",r:"6"}]],Gt=[["path",{d:"m14 12-8.381 8.38a1 1 0 0 1-3.001-3L11 9"}],["path",{d:"M15 15.5a.5.5 0 0 0 .5.5A6.5 6.5 0 0 0 22 9.5a.5.5 0 0 0-.5-.5h-1.672a2 2 0 0 1-1.414-.586l-5.062-5.062a1.205 1.205 0 0 0-1.704 0L9.352 5.648a1.205 1.205 0 0 0 0 1.704l5.062 5.062A2 2 0 0 1 15 13.828z"}]],f=[["path",{d:"M13.5 10.5 15 9"}],["path",{d:"M4 4v15a1 1 0 0 0 1 1h15"}],["path",{d:"M4.293 19.707 6 18"}],["path",{d:"m9 15 1.5-1.5"}]],Zt=[["path",{d:"M10 16c.5.3 1.2.5 2 .5s1.5-.2 2-.5"}],["path",{d:"M15 12h.01"}],["path",{d:"M19.38 6.813A9 9 0 0 1 20.8 10.2a2 2 0 0 1 0 3.6 9 9 0 0 1-17.6 0 2 2 0 0 1 0-3.6A9 9 0 0 1 12 3c2 0 3.5 1.1 3.5 2.5s-.9 2.5-2 2.5c-.8 0-1.5-.4-1.5-1"}],["path",{d:"M9 12h.01"}]],Et=[["path",{d:"M4 10a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z"}],["path",{d:"M8 10h8"}],["path",{d:"M8 18h8"}],["path",{d:"M8 22v-6a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v6"}],["path",{d:"M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"}]],Wt=[["path",{d:"M2.795 16.804c-.789-.79-1.09-2.018-.442-2.928 1.933-2.713 4.414-1.05 6.145-5.378 1.868-4.67 4.608-2.366 6.592-6.107.218-.412.78-.53 1.11-.2l5.005 5.005c.789.79 1.09 2.018.442 2.928-1.933 2.713-4.414 1.05-6.145 5.378-1.868 4.67-4.608 2.366-6.591 6.107-.22.412-.78.53-1.11.2z"}],["path",{d:"M4.5 18.5c2-5 5-2 7-7s5-2 7-7"}]],It=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16"}]],Xt=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"M12 7v10"}],["path",{d:"M15.4 10a4 4 0 1 0 0 4"}]],k=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"m16 9-5.5 5.5L8 12"}]],Nt=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"}],["path",{d:"M12 18V6"}]],Kt=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"M7 12h5"}],["path",{d:"M15 9.4a4 4 0 1 0 0 5.2"}]],Jt=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"M8 8h8"}],["path",{d:"M8 12h8"}],["path",{d:"m13 17-5-1h1a4 4 0 0 0 0-8"}]],Qt=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["line",{x1:"12",x2:"12",y1:"16",y2:"12"}],["line",{x1:"12",x2:"12.01",y1:"8",y2:"8"}]],jt=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"m9 8 3 3v7"}],["path",{d:"m12 11 3-3"}],["path",{d:"M9 12h6"}],["path",{d:"M9 16h6"}]],Yt=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["line",{x1:"8",x2:"16",y1:"12",y2:"12"}]],_t=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"m15 9-6 6"}],["path",{d:"M9 9h.01"}],["path",{d:"M15 15h.01"}]],xt=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["line",{x1:"12",x2:"12",y1:"8",y2:"16"}],["line",{x1:"8",x2:"16",y1:"12",y2:"12"}]],a5=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"M8 12h4"}],["path",{d:"M10 16V9.5a2.5 2.5 0 0 1 5 0"}],["path",{d:"M8 16h7"}]],P=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"}],["line",{x1:"12",x2:"12.01",y1:"17",y2:"17"}]],h5=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"M9 16h5"}],["path",{d:"M9 12h5a2 2 0 1 0 0-4h-3v9"}]],t5=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["path",{d:"M11 17V8h4"}],["path",{d:"M11 12h3"}],["path",{d:"M9 16h4"}]],d5=[["path",{d:"M11 7v10a5 5 0 0 0 5-5"}],["path",{d:"m15 8-6 3"}],["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76"}]],c5=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}],["line",{x1:"15",x2:"9",y1:"9",y2:"15"}],["line",{x1:"9",x2:"15",y1:"9",y2:"15"}]],M5=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"}]],p5=[["path",{d:"M22 18H6a2 2 0 0 1-2-2V7a2 2 0 0 0-2-2"}],["path",{d:"M17 14V4a2 2 0 0 0-2-2h-1a2 2 0 0 0-2 2v10"}],["rect",{width:"13",height:"8",x:"8",y:"6",rx:"1"}],["circle",{cx:"18",cy:"20",r:"2"}],["circle",{cx:"9",cy:"20",r:"2"}]],l5=[["path",{d:"M12 16v1a2 2 0 0 0 2 2h1a2 2 0 0 1 2 2v1"}],["path",{d:"M12 6a2 2 0 0 1 2 2"}],["path",{d:"M18 8c0 4-3.5 8-6 8s-6-4-6-8a6 6 0 0 1 12 0"}]],i5=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M4.929 4.929 19.07 19.071"}]],n5=[["path",{d:"M4 13c3.5-2 8-2 10 2a5.5 5.5 0 0 1 8 5"}],["path",{d:"M5.15 17.89c5.52-1.52 8.65-6.89 7-12C11.55 4 11.5 2 13 2c3.22 0 5 5.5 5 8 0 6.5-4.2 12-10.49 12C5.11 22 2 22 2 20c0-1.5 1.14-1.55 3.15-2.11Z"}]],e5=[["path",{d:"M10 10.01h.01"}],["path",{d:"M10 14.01h.01"}],["path",{d:"M14 10.01h.01"}],["path",{d:"M14 14.01h.01"}],["path",{d:"M18 6v12"}],["path",{d:"M6 6v12"}],["rect",{x:"2",y:"6",width:"20",height:"12",rx:"2"}]],r5=[["path",{d:"M6 5a2 2 0 0 1 4 0v12a4 4 0 0 0 8 0 2 2 0 0 0-4 0"}],["path",{d:"M6 9h12"}]],o5=[["path",{d:"M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5"}],["path",{d:"m16 19 3 3 3-3"}],["path",{d:"M18 12h.01"}],["path",{d:"M19 16v6"}],["path",{d:"M6 12h.01"}],["circle",{cx:"12",cy:"12",r:"2"}]],v5=[["path",{d:"M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5"}],["path",{d:"M18 12h.01"}],["path",{d:"M19 22v-6"}],["path",{d:"m22 19-3-3-3 3"}],["path",{d:"M6 12h.01"}],["circle",{cx:"12",cy:"12",r:"2"}]],$5=[["path",{d:"M11.748 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4.875"}],["path",{d:"m16 19 2 2 4-4"}],["path",{d:"M18 12h.01"}],["path",{d:"M6 12h.01"}],["circle",{cx:"12",cy:"12",r:"2"}]],m5=[["path",{d:"M13 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5"}],["path",{d:"m17 17 5 5"}],["path",{d:"M18 12h.01"}],["path",{d:"m22 17-5 5"}],["path",{d:"M6 12h.01"}],["circle",{cx:"12",cy:"12",r:"2"}]],y5=[["rect",{width:"20",height:"12",x:"2",y:"6",rx:"2"}],["circle",{cx:"12",cy:"12",r:"2"}],["path",{d:"M6 12h.01M18 12h.01"}]],s5=[["path",{d:"M3 5v14"}],["path",{d:"M8 5v14"}],["path",{d:"M12 5v14"}],["path",{d:"M17 5v14"}],["path",{d:"M21 5v14"}]],C5=[["path",{d:"M10.5 2a34.5 34.5 0 000 20"}],["path",{d:"M13.5 2a34.5 34.5 0 010 20"}],["path",{d:"M4.67 17h14.67"}],["path",{d:"M4.67 7h14.67"}],["path",{d:"M6.23 3.06a19 19 0 000 17.88A2 2 0 008 22h8a2 2 0 001.77-1.06 19 19 0 000-17.88A2 2 0 0016 2H8a2 2 0 00-1.77 1.06"}]],u5=[["path",{d:"M4 20h16"}],["path",{d:"m6 16 6-12 6 12"}],["path",{d:"M8 12h8"}]],g5=[["path",{d:"M10 4 8 6"}],["path",{d:"M17 19v2"}],["path",{d:"M2 12h20"}],["path",{d:"M7 19v2"}],["path",{d:"M9 5 7.621 3.621A2.121 2.121 0 0 0 4 5v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5"}]],A5=[["path",{d:"m11 7-3 5h4l-3 5"}],["path",{d:"M14.856 6H16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.935"}],["path",{d:"M22 14v-4"}],["path",{d:"M5.14 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.936"}]],H5=[["path",{d:"M10 10v4"}],["path",{d:"M14 10v4"}],["path",{d:"M22 14v-4"}],["path",{d:"M6 10v4"}],["rect",{x:"2",y:"6",width:"16",height:"12",rx:"2"}]],V5=[["path",{d:"M22 14v-4"}],["path",{d:"M6 14v-4"}],["rect",{x:"2",y:"6",width:"16",height:"12",rx:"2"}]],S5=[["path",{d:"M10 14v-4"}],["path",{d:"M22 14v-4"}],["path",{d:"M6 14v-4"}],["rect",{x:"2",y:"6",width:"16",height:"12",rx:"2"}]],w5=[["path",{d:"M10 9v6"}],["path",{d:"M12.543 6H16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-3.605"}],["path",{d:"M22 14v-4"}],["path",{d:"M7 12h6"}],["path",{d:"M7.606 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3.606"}]],L5=[["path",{d:"M10 17h.01"}],["path",{d:"M10 7v6"}],["path",{d:"M14 6h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2"}],["path",{d:"M22 14v-4"}],["path",{d:"M6 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2"}]],f5=[["path",{d:"M 22 14 L 22 10"}],["rect",{x:"2",y:"6",width:"16",height:"12",rx:"2"}]],k5=[["path",{d:"M4.5 3h15"}],["path",{d:"M6 3v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V3"}],["path",{d:"M6 14h12"}]],P5=[["path",{d:"M9 9c-.64.64-1.521.954-2.402 1.165A6 6 0 0 0 8 22a13.96 13.96 0 0 0 9.9-4.1"}],["path",{d:"M10.75 5.093A6 6 0 0 1 22 8c0 2.411-.61 4.68-1.683 6.66"}],["path",{d:"M5.341 10.62a4 4 0 0 0 6.487 1.208M10.62 5.341a4.015 4.015 0 0 1 2.039 2.04"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],B5=[["path",{d:"M10.165 6.598C9.954 7.478 9.64 8.36 9 9c-.64.64-1.521.954-2.402 1.165A6 6 0 0 0 8 22c7.732 0 14-6.268 14-14a6 6 0 0 0-11.835-1.402Z"}],["path",{d:"M5.341 10.62a4 4 0 1 0 5.279-5.28"}]],z5=[["path",{d:"M2 20v-8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v8"}],["path",{d:"M4 10V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4"}],["path",{d:"M12 4v6"}],["path",{d:"M2 18h20"}]],D5=[["path",{d:"M3 20v-8a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v8"}],["path",{d:"M5 10V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v4"}],["path",{d:"M3 18h18"}]],F5=[["path",{d:"M2 4v16"}],["path",{d:"M2 8h18a2 2 0 0 1 2 2v10"}],["path",{d:"M2 17h20"}],["path",{d:"M6 8v9"}]],R5=[["path",{d:"M11.771 6.109a2.5 2.5 0 0 1 3.12 3.12"}],["path",{d:"M17.852 12.185a6.5 6.5 0 0 0-9.035-9.04"}],["path",{d:"M18.013 18.013C15.029 20.349 10.831 22 7 22a3 3 0 0 1-2.68-1.66L2.4 16.5"}],["path",{d:"m18.5 6 2.19 4.5a6.48 6.48 0 0 1-.139 4.393"}],["path",{d:"m2 2 20 20"}],["path",{d:"M6.355 6.37a7 7 0 0 0-.075.23c-1.1 3.13-.78 3.9-3.18 6.08A3 3 0 0 0 5 18c3.356 0 6.993-1.267 9.85-3.151"}]],b5=[["path",{d:"M16.4 13.7A6.5 6.5 0 1 0 6.28 6.6c-1.1 3.13-.78 3.9-3.18 6.08A3 3 0 0 0 5 18c4 0 8.4-1.8 11.4-4.3"}],["path",{d:"m18.5 6 1.754 3.5a6.48 6.48 0 0 1-1.854 8.2C15.4 20.2 11 22 7 22a3 3 0 0 1-2.68-1.66L2.4 16.5"}],["circle",{cx:"12.5",cy:"8.5",r:"2.5"}]],T5=[["path",{d:"M13 13v5"}],["path",{d:"M17 11.47V8"}],["path",{d:"M17 11h1a3 3 0 0 1 2.745 4.211"}],["path",{d:"m2 2 20 20"}],["path",{d:"M5 8v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-3"}],["path",{d:"M7.536 7.535C6.766 7.649 6.154 8 5.5 8a2.5 2.5 0 0 1-1.768-4.268"}],["path",{d:"M8.727 3.204C9.306 2.767 9.885 2 11 2c1.56 0 2 1.5 3 1.5s1.72-.5 2.5-.5a1 1 0 1 1 0 5c-.78 0-1.5-.5-2.5-.5a3.149 3.149 0 0 0-.842.12"}],["path",{d:"M9 14.6V18"}]],q5=[["path",{d:"M17 11h1a3 3 0 0 1 0 6h-1"}],["path",{d:"M9 12v6"}],["path",{d:"M13 12v6"}],["path",{d:"M14 7.5c-1 0-1.44.5-3 .5s-2-.5-3-.5-1.72.5-2.5.5a2.5 2.5 0 0 1 0-5c.78 0 1.57.5 2.5.5S9.44 2 11 2s2 1.5 3 1.5 1.72-.5 2.5-.5a2.5 2.5 0 0 1 0 5c-.78 0-1.5-.5-2.5-.5Z"}],["path",{d:"M5 8v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V8"}]],U5=[["path",{d:"M3 16 L13 16"}],["path",{d:"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7"}]],O5=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0"}],["path",{d:"m15 8 2 2 4-4"}],["path",{d:"M16.8607 4.4824A6 6 0 0 0 6 8C6 12.499 4.589 13.956 3.262 15.326"}],["path",{d:"M3.262 15.326A1 1 0 0 0 4 17H20A1 1 0 0 0 20.74 15.327C20.209 14.779 19.665 14.218 19.203 13.454"}]],G5=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0"}],["path",{d:"M11.68 2.009A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673c-.824-.85-1.678-1.731-2.21-3.348"}],["circle",{cx:"18",cy:"5",r:"3"}]],Z5=[["path",{d:"M18.518 17.347A7 7 0 0 1 14 19"}],["path",{d:"M18.8 4A11 11 0 0 1 20 9"}],["path",{d:"M9 9h.01"}],["circle",{cx:"20",cy:"16",r:"2"}],["circle",{cx:"9",cy:"9",r:"7"}],["rect",{x:"4",y:"16",width:"10",height:"6",rx:"2"}]],E5=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0"}],["path",{d:"M15 8h6"}],["path",{d:"M16.243 3.757A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673A9.4 9.4 0 0 1 18.667 12"}]],W5=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0"}],["path",{d:"M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05"}]],I5=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0"}],["path",{d:"M15 8h6"}],["path",{d:"M18 5v6"}],["path",{d:"M20.002 14.464a9 9 0 0 0 .738.863A1 1 0 0 1 20 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 8.75-5.332"}]],X5=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0"}],["path",{d:"M22 8c0-2.3-.8-4.3-2-6"}],["path",{d:"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"}],["path",{d:"M4 2C2.8 3.7 2 5.7 2 8"}]],N5=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0"}],["path",{d:"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"}]],B=[["rect",{width:"13",height:"7",x:"3",y:"3",rx:"1"}],["path",{d:"m22 15-3-3 3-3"}],["rect",{width:"13",height:"7",x:"3",y:"14",rx:"1"}]],z=[["rect",{width:"13",height:"7",x:"8",y:"3",rx:"1"}],["path",{d:"m2 9 3 3-3 3"}],["rect",{width:"13",height:"7",x:"8",y:"14",rx:"1"}]],K5=[["rect",{width:"7",height:"13",x:"3",y:"3",rx:"1"}],["path",{d:"m9 22 3-3 3 3"}],["rect",{width:"7",height:"13",x:"14",y:"3",rx:"1"}]],J5=[["rect",{width:"7",height:"13",x:"3",y:"8",rx:"1"}],["path",{d:"m15 2-3 3-3-3"}],["rect",{width:"7",height:"13",x:"14",y:"8",rx:"1"}]],Q5=[["path",{d:"M12.409 13.017A5 5 0 0 1 22 15c0 3.866-4 7-9 7-4.077 0-8.153-.82-10.371-2.462-.426-.316-.631-.832-.62-1.362C2.118 12.723 2.627 2 10 2a3 3 0 0 1 3 3 2 2 0 0 1-2 2c-1.105 0-1.64-.444-2-1"}],["path",{d:"M15 14a5 5 0 0 0-7.584 2"}],["path",{d:"M9.964 6.825C8.019 7.977 9.5 13 8 15"}]],j5=[["circle",{cx:"18.5",cy:"17.5",r:"3.5"}],["circle",{cx:"5.5",cy:"17.5",r:"3.5"}],["circle",{cx:"15",cy:"5",r:"1"}],["path",{d:"M12 17.5V14l-3-3 4-3 2 3h2"}]],Y5=[["rect",{x:"14",y:"14",width:"4",height:"6",rx:"2"}],["rect",{x:"6",y:"4",width:"4",height:"6",rx:"2"}],["path",{d:"M6 20h4"}],["path",{d:"M14 10h4"}],["path",{d:"M6 14h2v6"}],["path",{d:"M14 4h2v6"}]],_5=[["circle",{cx:"12",cy:"11.9",r:"2"}],["path",{d:"M6.7 3.4c-.9 2.5 0 5.2 2.2 6.7C6.5 9 3.7 9.6 2 11.6"}],["path",{d:"m8.9 10.1 1.4.8"}],["path",{d:"M17.3 3.4c.9 2.5 0 5.2-2.2 6.7 2.4-1.2 5.2-.6 6.9 1.5"}],["path",{d:"m15.1 10.1-1.4.8"}],["path",{d:"M16.7 20.8c-2.6-.4-4.6-2.6-4.7-5.3-.2 2.6-2.1 4.8-4.7 5.2"}],["path",{d:"M12 13.9v1.6"}],["path",{d:"M13.5 5.4c-1-.2-2-.2-3 0"}],["path",{d:"M17 16.4c.7-.7 1.2-1.6 1.5-2.5"}],["path",{d:"M5.5 13.9c.3.9.8 1.8 1.5 2.5"}]],x5=[["path",{d:"M10 10h4"}],["path",{d:"M19 7V4a1 1 0 0 0-1-1h-2a1 1 0 0 0-1 1v3"}],["path",{d:"M20 21a2 2 0 0 0 2-2v-3.851c0-1.39-2-2.962-2-4.829V8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v11a2 2 0 0 0 2 2z"}],["path",{d:"M 22 16 L 2 16"}],["path",{d:"M4 21a2 2 0 0 1-2-2v-3.851c0-1.39 2-2.962 2-4.829V8a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v11a2 2 0 0 1-2 2z"}],["path",{d:"M9 7V4a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1v3"}]],a4=[["path",{d:"M16 7h.01"}],["path",{d:"M3.4 18H12a8 8 0 0 0 8-8V7a4 4 0 0 0-7.28-2.3L2 20"}],["path",{d:"m20 7 2 .5-2 .5"}],["path",{d:"M10 18v3"}],["path",{d:"M14 17.75V21"}],["path",{d:"M7 18a6 6 0 0 0 3.84-10.61"}]],h4=[["path",{d:"M12 18v4"}],["path",{d:"m17 18 1.956-11.468"}],["path",{d:"m3 8 7.82-5.615a2 2 0 0 1 2.36 0L21 8"}],["path",{d:"M4 18h16"}],["path",{d:"M7 18 5.044 6.532"}],["circle",{cx:"12",cy:"10",r:"2"}]],t4=[["path",{d:"M11.767 19.089c4.924.868 6.14-6.025 1.216-6.894m-1.216 6.894L5.86 18.047m5.908 1.042-.347 1.97m1.563-8.864c4.924.869 6.14-6.025 1.215-6.893m-1.215 6.893-3.94-.694m5.155-6.2L8.29 4.26m5.908 1.042.348-1.97M7.48 20.364l3.126-17.727"}]],d4=[["circle",{cx:"15",cy:"9",r:"7"}],["circle",{cx:"9",cy:"15",r:"7"}]],c4=[["path",{d:"M8 14a2 2 0 0 0-1.963 1.615l-1.018 5.193A1 1 0 0 0 6 22h12a1 1 0 0 0 .981-1.192l-1.018-5.193A2 2 0 0 0 16 14z"}],["path",{d:"m17 2-1 12"}],["path",{d:"M8.006 14 7 2"}],["path",{d:"M7.565 8.787A5 5 0 0 0 12 8a5 5 0 0 1 4.56-.75"}],["path",{d:"M19 2H5a2 2 0 0 0-2 2v5a2 2 0 0 0 .688 1.5"}],["path",{d:"M12 18h.01"}]],M4=[["path",{d:"M3 3h18"}],["path",{d:"M20 7H8"}],["path",{d:"M20 11H8"}],["path",{d:"M10 19h10"}],["path",{d:"M8 15h12"}],["path",{d:"M4 3v14"}],["circle",{cx:"4",cy:"19",r:"2"}]],p4=[["path",{d:"M10 22V7a1 1 0 0 0-1-1H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5a1 1 0 0 0-1-1H2"}],["rect",{x:"14",y:"2",width:"8",height:"8",rx:"1"}]],l4=[["path",{d:"m7 7 10 10-5 5V2l5 5L7 17"}],["line",{x1:"18",x2:"21",y1:"12",y2:"12"}],["line",{x1:"3",x2:"6",y1:"12",y2:"12"}]],i4=[["path",{d:"m17 17-5 5V12l-5 5"}],["path",{d:"m2 2 20 20"}],["path",{d:"M14.5 9.5 17 7l-5-5v4.5"}]],n4=[["path",{d:"m7 7 10 10-5 5V2l5 5L7 17"}],["path",{d:"M20.83 14.83a4 4 0 0 0 0-5.66"}],["path",{d:"M18 12h.01"}]],e4=[["path",{d:"m7 7 10 10-5 5V2l5 5L7 17"}]],r4=[["path",{d:"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"}]],o4=[["path",{d:"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"}],["circle",{cx:"12",cy:"12",r:"4"}]],v4=[["circle",{cx:"11",cy:"13",r:"9"}],["path",{d:"M14.35 4.65 16.3 2.7a2.41 2.41 0 0 1 3.4 0l1.6 1.6a2.4 2.4 0 0 1 0 3.4l-1.95 1.95"}],["path",{d:"m22 2-1.5 1.5"}]],$4=[["path",{d:"M14 4.5a1 1 0 0 1 5 0 .5.5 0 0 0 .5.5 1 1 0 0 1 0 5c-.81 0-1.8-.7-2.5 0l-1.958 1.957a.15.15 0 0 1-.252-.072l-.493-2.07a.15.15 0 0 0-.111-.112l-2.072-.494a.15.15 0 0 1-.072-.252L14 7c.7-.7 0-1.69 0-2.5"}],["path",{d:"m16 20-1-2"}],["path",{d:"m20 16-2-1"}],["path",{d:"m4 8 2 1"}],["path",{d:"m8 4 1 2"}],["path",{d:"M9.698 14.19a.15.15 0 0 0 .112.112l2.074.489a.15.15 0 0 1 .072.252L10 17c-.7.7 0 1.69 0 2.5a1 1 0 0 1-5 0 .495.495 0 0 0-.5-.5 1 1 0 0 1 0-5c.81 0 1.8.7 2.5 0l1.956-1.957a.15.15 0 0 1 .252.072z"}]],m4=[["path",{d:"M17 10c.7-.7 1.69 0 2.5 0a2.5 2.5 0 1 0 0-5 .5.5 0 0 1-.5-.5 2.5 2.5 0 1 0-5 0c0 .81.7 1.8 0 2.5l-7 7c-.7.7-1.69 0-2.5 0a2.5 2.5 0 0 0 0 5c.28 0 .5.22.5.5a2.5 2.5 0 1 0 5 0c0-.81-.7-1.8 0-2.5Z"}]],y4=[["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"m8 13 4-7 4 7"}],["path",{d:"M9.1 11h5.7"}]],s4=[["path",{d:"M12 13h.01"}],["path",{d:"M12 6v3"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}]],C4=[["path",{d:"M12 6v7"}],["path",{d:"M16 8v3"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"M8 8v3"}]],D=[["path",{d:"M10 2v7.751a.25.25 0 00.407.195l2.28-1.834a.5.5 0 01.627 0l2.28 1.834A.25.25 0 0016 9.751V2"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 016.5 2H19a1 1 0 011 1v18a1 1 0 01-1 1H6.5a1 1 0 010-5H20"}]],u4=[["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"m9 9.5 2 2 4-4"}]],g4=[["path",{d:"M5 7a2 2 0 0 0-2 2v11"}],["path",{d:"M5.803 18H5a2 2 0 0 0 0 4h9.5a.5.5 0 0 0 .5-.5V21"}],["path",{d:"M9 15V4a2 2 0 0 1 2-2h9.5a.5.5 0 0 1 .5.5v14a.5.5 0 0 1-.5.5H11a2 2 0 0 1 0-4h10"}]],F=[["path",{d:"M12 17h1.5"}],["path",{d:"M12 22h1.5"}],["path",{d:"M12 2h1.5"}],["path",{d:"M17.5 22H19a1 1 0 0 0 1-1"}],["path",{d:"M17.5 2H19a1 1 0 0 1 1 1v1.5"}],["path",{d:"M20 14v3h-2.5"}],["path",{d:"M20 8.5V10"}],["path",{d:"M4 10V8.5"}],["path",{d:"M4 19.5V14"}],["path",{d:"M4 4.5A2.5 2.5 0 0 1 6.5 2H8"}],["path",{d:"M8 22H6.5a1 1 0 0 1 0-5H8"}]],A4=[["path",{d:"M12 13V7"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"m9 10 3 3 3-3"}]],H4=[["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"M8 12v-2a4 4 0 0 1 8 0v2"}],["circle",{cx:"15",cy:"12",r:"1"}],["circle",{cx:"9",cy:"12",r:"1"}]],V4=[["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"M8.62 9.8A2.25 2.25 0 1 1 12 6.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a.998.998 0 0 1-1.507 0z"}]],S4=[["path",{d:"m20 13.7-2.1-2.1a2 2 0 0 0-2.8 0L9.7 17"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["circle",{cx:"10",cy:"8",r:"2"}]],w4=[["path",{d:"M13 2H6.5A2.5 2.5 0 0 0 4 4.5v15"}],["path",{d:"M17 2v6"}],["path",{d:"M17 4h2"}],["path",{d:"M20 15.2V21a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["circle",{cx:"17",cy:"10",r:"2"}]],L4=[["path",{d:"M18 6V4a2 2 0 1 0-4 0v2"}],["path",{d:"M20 15v6a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H10"}],["rect",{x:"12",y:"6",width:"8",height:"5",rx:"1"}]],f4=[["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"M9 10h6"}]],k4=[["path",{d:"M12 5v16"}],["path",{d:"m16 12 2 2 4-4"}],["path",{d:"M22 6V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2h4.001A2 2 0 0022 17v-1.344"}]],P4=[["path",{d:"M12 5v16"}],["path",{d:"M16 13h2"}],["path",{d:"M16 9h2"}],["path",{d:"M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z"}],["path",{d:"M6 13h2"}],["path",{d:"M6 9h2"}]],B4=[["path",{d:"M12 5v16"}],["path",{d:"M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z"}]],z4=[["path",{d:"M11 22H5.5a1 1 0 0 1 0-5h4.501"}],["path",{d:"m21 22-1.879-1.878"}],["path",{d:"M3 19.5v-15A2.5 2.5 0 0 1 5.5 2H18a1 1 0 0 1 1 1v8"}],["circle",{cx:"17",cy:"18",r:"3"}]],D4=[["path",{d:"M12 7v6"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"M9 10h6"}]],F4=[["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"M8 11h8"}],["path",{d:"M8 7h6"}]],R4=[["path",{d:"M10 13h4"}],["path",{d:"M12 6v7"}],["path",{d:"M16 8V6H8v2"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}]],b4=[["path",{d:"M12 13V7"}],["path",{d:"M18 2h1a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2"}],["path",{d:"m9 10 3-3 3 3"}],["path",{d:"m9 5 3-3 3 3"}]],T4=[["path",{d:"M12 13V7"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"m9 10 3-3 3 3"}]],q4=[["path",{d:"M15 13a3 3 0 1 0-6 0"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["circle",{cx:"12",cy:"8",r:"2"}]],U4=[["path",{d:"m14.5 7.5-5 5"}],["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}],["path",{d:"m9.5 7.5 5 5"}]],O4=[["path",{d:"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"}]],G4=[["path",{d:"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z"}],["path",{d:"m9 10 2 2 4-4"}]],Z4=[["path",{d:"M15 10H9"}],["path",{d:"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z"}]],E4=[["path",{d:"M19 19v1a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8.656 3H17a2 2 0 0 1 2 2v8.344"}]],W4=[["path",{d:"M12 7v6"}],["path",{d:"M15 10H9"}],["path",{d:"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z"}]],I4=[["path",{d:"m14.5 7.5-5 5"}],["path",{d:"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z"}],["path",{d:"m9.5 7.5 5 5"}]],X4=[["path",{d:"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z"}]],N4=[["path",{d:"M4 9V5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4"}],["path",{d:"M8 8v1"}],["path",{d:"M12 8v1"}],["path",{d:"M16 8v1"}],["rect",{width:"20",height:"12",x:"2",y:"9",rx:"2"}],["circle",{cx:"8",cy:"15",r:"2"}],["circle",{cx:"16",cy:"15",r:"2"}]],K4=[["path",{d:"M12 6V2H8"}],["path",{d:"M15 11v2"}],["path",{d:"M2 12h2"}],["path",{d:"M20 12h2"}],["path",{d:"M20 16a2 2 0 0 1-2 2H8.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 4 20.286V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2z"}],["path",{d:"M9 11v2"}]],J4=[["path",{d:"M13.67 8H18a2 2 0 0 1 2 2v4.33"}],["path",{d:"M2 14h2"}],["path",{d:"M20 14h2"}],["path",{d:"M22 22 2 2"}],["path",{d:"M8 8H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 1.414-.586"}],["path",{d:"M9 13v2"}],["path",{d:"M9.67 4H12v2.33"}]],Q4=[["path",{d:"M12 8V4H8"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2"}],["path",{d:"M2 14h2"}],["path",{d:"M20 14h2"}],["path",{d:"M15 13v2"}],["path",{d:"M9 13v2"}]],j4=[["path",{d:"M10 3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a6 6 0 0 0 1.2 3.6l.6.8A6 6 0 0 1 17 13v8a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-8a6 6 0 0 1 1.2-3.6l.6-.8A6 6 0 0 0 10 5z"}],["path",{d:"M17 13h-4a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h4"}]],Y4=[["path",{d:"M17 3h4v4"}],["path",{d:"M18.575 11.082a13 13 0 0 1 1.048 9.027 1.17 1.17 0 0 1-1.914.597L14 17"}],["path",{d:"M7 10 3.29 6.29a1.17 1.17 0 0 1 .6-1.91 13 13 0 0 1 9.03 1.05"}],["path",{d:"M7 14a1.7 1.7 0 0 0-1.207.5l-2.646 2.646A.5.5 0 0 0 3.5 18H5a1 1 0 0 1 1 1v1.5a.5.5 0 0 0 .854.354L9.5 18.207A1.7 1.7 0 0 0 10 17v-2a1 1 0 0 0-1-1z"}],["path",{d:"M9.707 14.293 21 3"}]],_4=[["path",{d:"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"}],["path",{d:"m3.3 7 8.7 5 8.7-5"}],["path",{d:"M12 22V12"}]],x4=[["path",{d:"M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"}],["path",{d:"m7 16.5-4.74-2.85"}],["path",{d:"m7 16.5 5-3"}],["path",{d:"M7 16.5v5.17"}],["path",{d:"M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"}],["path",{d:"m17 16.5-5-3"}],["path",{d:"m17 16.5 4.74-2.85"}],["path",{d:"M17 16.5v5.17"}],["path",{d:"M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"}],["path",{d:"M12 8 7.26 5.15"}],["path",{d:"m12 8 4.74-2.85"}],["path",{d:"M12 13.5V8"}]],R=[["path",{d:"M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1"}],["path",{d:"M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"}]],a3=[["path",{d:"M16 3h3a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-3"}],["path",{d:"M8 21H5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h3"}]],h3=[["path",{d:"M12 13h4"}],["path",{d:"M12 18h6a2 2 0 012 2v1"}],["path",{d:"M12 8h8"}],["path",{d:"M12 9a4.17 4.17 0 01-3 4"}],["path",{d:"M16 8V5a2 2 0 012-2"}],["path",{d:"M6 18a4 4 0 01-2-7.465"}],["path",{d:"M6.004 5.125a4 4 0 00-2.526 5.77"}],["path",{d:"M6.294 6.294A3 3 0 1112 5v13a4 4 0 11-7.967-.518"}],["circle",{cx:"16",cy:"13",r:"0.5",fill:"currentColor"}],["circle",{cx:"18",cy:"3",r:"0.5",fill:"currentColor"}],["circle",{cx:"20",cy:"21",r:"0.5",fill:"currentColor"}],["circle",{cx:"20",cy:"8",r:"0.5",fill:"currentColor"}]],t3=[["path",{d:"m10.852 14.772-.383.923"}],["path",{d:"m10.852 9.228-.383-.923"}],["path",{d:"m13.148 14.772.382.924"}],["path",{d:"m13.53 8.305-.382.923"}],["path",{d:"m14.772 10.852.923-.383"}],["path",{d:"m14.772 13.148.923.383"}],["path",{d:"M17.997 5.125a4 4 0 012.526 5.77"}],["path",{d:"M18 18a4 4 0 002-7.464"}],["path",{d:"M4.033 17.483A4 4 0 0011.46 20c.184-.31.896-.31 1.076 0a4 4 0 007.43-2.517"}],["path",{d:"M6 18a4 4 0 01-2-7.464"}],["path",{d:"M6.002 5.125a4 4 0 00-2.525 5.77"}],["path",{d:"M6.293 6.294a3 3 0 01-.287-1.098l-.004-.07a3 3 0 015.968-.543c.05.02.057.01.059 0a3 3 0 015.968.542l-.004.071a3 3 0 01-.287 1.098"}],["path",{d:"m9.228 10.852-.923-.383"}],["path",{d:"m9.228 13.148-.923.383"}],["circle",{cx:"12",cy:"12",r:"3"}]],d3=[["path",{d:"M12 18v-6"}],["path",{d:"M12 5v1.344"}],["path",{d:"M17.707 6.293A3 3 0 1012 5a3 3 0 00-4.124-2.781"}],["path",{d:"M17.997 5.125a4 4 0 012.526 5.77"}],["path",{d:"M19.644 19.644A4 4 0 0112 18a4 4 0 11-7.967-.517"}],["path",{d:"m2 2 20 20"}],["path",{d:"M20 10.536a4 4 0 011.533 5.34"}],["path",{d:"M3.477 10.895a4 4 0 011.876-5.542"}],["path",{d:"M6 18a4 4 0 01-2-7.464"}],["path",{d:"M9 13a4.17 4.17 0 002.166-1.5 4 4 0 00.138-.196"}]],c3=[["path",{d:"M12 18V5"}],["path",{d:"M15 13a4.17 4.17 0 01-3-4 4.17 4.17 0 01-3 4"}],["path",{d:"M17.707 6.294A3 3 0 1012 5a3 3 0 10-5.707 1.294"}],["path",{d:"M17.996 5.125a4 4 0 012.526 5.77"}],["path",{d:"M18 18a4 4 0 002-7.465"}],["path",{d:"M19.967 17.482a4 4 0 11-7.967.517 4 4 0 11-7.967-.517"}],["path",{d:"M6 18a4 4 0 01-2-7.465"}],["path",{d:"M6.004 5.125a4 4 0 00-2.526 5.77"}]],M3=[["path",{d:"M16 3v2.107"}],["path",{d:"M17 9c1 3 2.5 3.5 3.5 4.5A5 5 0 0 1 22 17a5 5 0 0 1-10 0c0-.3 0-.6.1-.9a2 2 0 1 0 3.3-2C13 11.5 16 9 17 9"}],["path",{d:"M21 8.274V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.938"}],["path",{d:"M3 15h5.253"}],["path",{d:"M3 9h8.228"}],["path",{d:"M8 15v6"}],["path",{d:"M8 3v6"}]],p3=[["path",{d:"M12 9v1.258"}],["path",{d:"M16 3v5.46"}],["path",{d:"M21 9.118V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5.75"}],["path",{d:"M22 17.5c0 2.499-1.75 3.749-3.83 4.474a.5.5 0 0 1-.335-.005c-2.085-.72-3.835-1.97-3.835-4.47V14a.5.5 0 0 1 .5-.499c1 0 2.25-.6 3.12-1.36a.6.6 0 0 1 .76-.001c.875.765 2.12 1.36 3.12 1.36a.5.5 0 0 1 .5.5z"}],["path",{d:"M3 15h7"}],["path",{d:"M3 9h12.142"}],["path",{d:"M8 15v6"}],["path",{d:"M8 3v6"}]],l3=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M12 9v6"}],["path",{d:"M16 15v6"}],["path",{d:"M16 3v6"}],["path",{d:"M3 15h18"}],["path",{d:"M3 9h18"}],["path",{d:"M8 15v6"}],["path",{d:"M8 3v6"}]],i3=[["path",{d:"M10 9.728V16"}],["path",{d:"M14 9.728V16"}],["path",{d:"M18 20V4"}],["path",{d:"m22 11-4-4A7.5 7.5 0 0 1 6 7l-4 4"}],["path",{d:"M22 16H2"}],["path",{d:"M6 20V4"}]],n3=[["path",{d:"M12 12h.01"}],["path",{d:"M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"}],["path",{d:"M22 13a18.15 18.15 0 0 1-20 0"}],["rect",{width:"20",height:"14",x:"2",y:"6",rx:"2"}]],e3=[["path",{d:"M10 20v2"}],["path",{d:"M14 20v2"}],["path",{d:"M18 20v2"}],["path",{d:"M21 20H3"}],["path",{d:"M6 20v2"}],["path",{d:"M8 16V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v12"}],["rect",{x:"4",y:"6",width:"16",height:"10",rx:"2"}]],r3=[["path",{d:"M12 11v4"}],["path",{d:"M14 13h-4"}],["path",{d:"M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"}],["path",{d:"M18 6v14"}],["path",{d:"M6 6v14"}],["rect",{width:"20",height:"14",x:"2",y:"6",rx:"2"}]],o3=[["path",{d:"M13.354 20H4a2 2 0 01-2-2V8a2 2 0 012-2h16a2 2 0 012 2v3.354"}],["path",{d:"M16 11.354V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v16"}],["path",{d:"M16 17h6"}],["path",{d:"M19 14v6"}]],v3=[["path",{d:"M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"}],["rect",{width:"20",height:"14",x:"2",y:"6",rx:"2"}]],$3=[["rect",{x:"8",y:"8",width:"8",height:"8",rx:"2"}],["path",{d:"M4 10a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2"}],["path",{d:"M14 20a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2"}]],m3=[["path",{d:"M10 13a3 3 0 01-2.121-5.121"}],["path",{d:"M15.606 14.204c-3.5 1.5-5.899 4.503-8.899 7.503A1 1 0 016 22c-2 0-4-2-4-4a1 1 0 01.293-.707c1.911-1.911 3.823-3.578 5.347-5.441"}],["path",{d:"M16.573 14.737A4 4 0 0114 11"}],["path",{d:"M7.14 10.907a4 4 0 112.756-7.43A4 4 0 0116.7 4.48a2 2 0 012.82 2.82 4 4 0 011.002 6.805 4 4 0 11-7.51 1.59"}]],y3=[["path",{d:"M11 2v2"}],["path",{d:"M12 3h-2"}],["path",{d:"M13.5 10.5 22 2"}],["path",{d:"M14.734 13.841a2 2 0 00-.314-2.42L12.58 9.58a2 2 0 00-2.421-.314l-7.657 4.461A1 1 0 002.3 15.3l6.403 6.403a1 1 0 001.571-.204z"}],["path",{d:"M20 15v4"}],["path",{d:"M22 17h-4"}],["path",{d:"M4 4v4"}],["path",{d:"m5 18 2-2"}],["path",{d:"M6 6H2"}],["path",{d:"m7.699 10.7 5.602 5.601"}]],s3=[["path",{d:"m16 22-1-4"}],["path",{d:"M19 14a1 1 0 0 0 1-1v-1a2 2 0 0 0-2-2h-3a1 1 0 0 1-1-1V4a2 2 0 0 0-4 0v5a1 1 0 0 1-1 1H6a2 2 0 0 0-2 2v1a1 1 0 0 0 1 1"}],["path",{d:"M19 14H5l-1.973 6.767A1 1 0 0 0 4 22h16a1 1 0 0 0 .973-1.233z"}],["path",{d:"m8 22 1-4"}]],C3=[["path",{d:"M13.5 10.5 22 2"}],["path",{d:"M14.734 13.841a2 2 0 00-.314-2.42L12.58 9.58a2 2 0 00-2.421-.314l-7.657 4.461A1 1 0 002.3 15.3l6.403 6.403a1 1 0 001.571-.204z"}],["path",{d:"m5 18 2-2"}],["path",{d:"m7.699 10.7 5.602 5.601"}]],u3=[["path",{d:"m11 10 3 3"}],["path",{d:"M6.5 21A3.5 3.5 0 1 0 3 17.5a2.62 2.62 0 0 1-.708 1.792A1 1 0 0 0 3 21z"}],["path",{d:"M9.969 17.031 21.378 5.624a1 1 0 0 0-3.002-3.002L6.967 14.031"}]],g3=[["path",{d:"M7.001 15.085A1.5 1.5 0 0 1 9 16.5"}],["circle",{cx:"18.5",cy:"8.5",r:"3.5"}],["circle",{cx:"7.5",cy:"16.5",r:"5.5"}],["circle",{cx:"7.5",cy:"4.5",r:"2.5"}]],A3=[["path",{d:"M12 20v-8"}],["path",{d:"M12.656 7H14a4 4 0 0 1 4 4v1.344"}],["path",{d:"M14.12 3.88 16 2"}],["path",{d:"M17.123 17.123A6 6 0 0 1 6 14v-3a4 4 0 0 1 1.72-3.287"}],["path",{d:"m2 2 20 20"}],["path",{d:"M21 5a4 4 0 0 1-3.55 3.97"}],["path",{d:"M22 13h-3.344"}],["path",{d:"M3 21a4 4 0 0 1 3.81-4"}],["path",{d:"M3 5a4 4 0 0 0 3.55 3.97"}],["path",{d:"M6 13H2"}],["path",{d:"m8 2 1.88 1.88"}],["path",{d:"M9.712 4.06A3 3 0 0 1 15 6v1.13"}]],H3=[["path",{d:"M12 20v-9"}],["path",{d:"M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z"}],["path",{d:"M14.12 3.88 16 2"}],["path",{d:"M21 21a4 4 0 0 0-3.81-4"}],["path",{d:"M21 5a4 4 0 0 1-3.55 3.97"}],["path",{d:"M22 13h-4"}],["path",{d:"M3 21a4 4 0 0 1 3.81-4"}],["path",{d:"M3 5a4 4 0 0 0 3.55 3.97"}],["path",{d:"M6 13H2"}],["path",{d:"m8 2 1.88 1.88"}],["path",{d:"M9 7.13V6a3 3 0 1 1 6 0v1.13"}]],V3=[["path",{d:"M10 19.655A6 6 0 0 1 6 14v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 3.97"}],["path",{d:"M14 15.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"}],["path",{d:"M14.12 3.88 16 2"}],["path",{d:"M21 5a4 4 0 0 1-3.55 3.97"}],["path",{d:"M3 21a4 4 0 0 1 3.81-4"}],["path",{d:"M3 5a4 4 0 0 0 3.55 3.97"}],["path",{d:"M6 13H2"}],["path",{d:"m8 2 1.88 1.88"}],["path",{d:"M9 7.13V6a3 3 0 1 1 6 0v1.13"}]],S3=[["path",{d:"M10 12h4"}],["path",{d:"M10 21v-3a2 2 0 013.05-1.702"}],["path",{d:"M10 8h4"}],["path",{d:"M16 19h6"}],["path",{d:"M18 7h2a2 2 0 012 2v4.355"}],["path",{d:"M19 16v6"}],["path",{d:"M6 10H4a2 2 0 00-2 2v7a2 2 0 002 2h8.535"}],["path",{d:"M6 21V5a2 2 0 012-2h8a2 2 0 012 2v7.126"}]],b=[["path",{d:"M10 12h4"}],["path",{d:"M10 8h4"}],["path",{d:"M14 21v-3a2 2 0 0 0-4 0v3"}],["path",{d:"M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2"}],["path",{d:"M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16"}]],w3=[["path",{d:"M12 10h.01"}],["path",{d:"M12 14h.01"}],["path",{d:"M12 6h.01"}],["path",{d:"M16 10h.01"}],["path",{d:"M16 14h.01"}],["path",{d:"M16 6h.01"}],["path",{d:"M8 10h.01"}],["path",{d:"M8 14h.01"}],["path",{d:"M8 6h.01"}],["path",{d:"M9 22v-3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3"}],["rect",{x:"4",y:"2",width:"16",height:"20",rx:"2"}]],L3=[["path",{d:"M4 6 2 7"}],["path",{d:"M10 6h4"}],["path",{d:"m22 7-2-1"}],["rect",{width:"16",height:"16",x:"4",y:"3",rx:"2"}],["path",{d:"M4 11h16"}],["path",{d:"M8 15h.01"}],["path",{d:"M16 15h.01"}],["path",{d:"M6 19v2"}],["path",{d:"M18 21v-2"}]],f3=[["path",{d:"M17 19a1 1 0 0 1-1-1v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2a1 1 0 0 1-1 1z"}],["path",{d:"M17 21v-2"}],["path",{d:"M19 14V6.5a1 1 0 0 0-7 0v11a1 1 0 0 1-7 0V10"}],["path",{d:"M21 21v-2"}],["path",{d:"M3 5V3"}],["path",{d:"M4 10a2 2 0 0 1-2-2V6a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2a2 2 0 0 1-2 2z"}],["path",{d:"M7 5V3"}]],k3=[["path",{d:"M8 6v6"}],["path",{d:"M15 6v6"}],["path",{d:"M2 12h19.6"}],["path",{d:"M18 18h3s.5-1.7.8-2.8c.1-.4.2-.8.2-1.2 0-.4-.1-.8-.2-1.2l-1.4-5C20.1 6.8 19.1 6 18 6H4a2 2 0 0 0-2 2v10h3"}],["circle",{cx:"7",cy:"18",r:"2"}],["path",{d:"M9 18h5"}],["circle",{cx:"16",cy:"18",r:"2"}]],P3=[["path",{d:"M10 3h.01"}],["path",{d:"M14 2h.01"}],["path",{d:"m2 9 20-5"}],["path",{d:"M12 12V6.5"}],["rect",{width:"16",height:"10",x:"4",y:"12",rx:"3"}],["path",{d:"M9 12v5"}],["path",{d:"M15 12v5"}],["path",{d:"M4 17h16"}]],B3=[["path",{d:"M16 13H3"}],["path",{d:"M16 17H3"}],["path",{d:"m7.2 7.9-3.388 2.5A2 2 0 0 0 3 12.01V20a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-8.654c0-2-2.44-6.026-6.44-8.026a1 1 0 0 0-1.082.057L10.4 5.6"}],["circle",{cx:"9",cy:"7",r:"2"}]],z3=[["path",{d:"M20 21v-8a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8"}],["path",{d:"M4 16s.5-1 2-1 2.5 2 4 2 2.5-2 4-2 2.5 2 4 2 2-1 2-1"}],["path",{d:"M2 21h20"}],["path",{d:"M7 8v3"}],["path",{d:"M12 8v3"}],["path",{d:"M17 8v3"}],["path",{d:"M7 4h.01"}],["path",{d:"M12 4h.01"}],["path",{d:"M17 4h.01"}]],D3=[["rect",{width:"16",height:"20",x:"4",y:"2",rx:"2"}],["line",{x1:"8",x2:"16",y1:"6",y2:"6"}],["line",{x1:"16",x2:"16",y1:"14",y2:"18"}],["path",{d:"M16 10h.01"}],["path",{d:"M12 10h.01"}],["path",{d:"M8 10h.01"}],["path",{d:"M12 14h.01"}],["path",{d:"M8 14h.01"}],["path",{d:"M12 18h.01"}],["path",{d:"M8 18h.01"}]],F3=[["path",{d:"M11 13h1v4"}],["path",{d:"M16 2v3"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],R3=[["path",{d:"m14 17 4 4 4-4"}],["path",{d:"M16 2v3"}],["path",{d:"M18 13v8"}],["path",{d:"M21 10.354V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h7.343"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}]],b3=[["path",{d:"m14 17 4-4 4 4"}],["path",{d:"M16 2v3"}],["path",{d:"M18 21v-8"}],["path",{d:"M21 10.343V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h9"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}]],T3=[["path",{d:"M 19 3 L 5 3"}],["path",{d:"M 21 13 L 21 5"}],["path",{d:"M 21 5 A2 2 0 0 0 19 3"}],["path",{d:"M 3 19 A2 2 0 0 0 5 21"}],["path",{d:"M 3 5 L 3 19"}],["path",{d:"M 5 3 A2 2 0 0 0 3 5"}],["path",{d:"m16 19 2 2 4-4"}],["path",{d:"M16 2v3"}],["path",{d:"M3 9h18"}],["path",{d:"M5 21 L12.5 21"}],["path",{d:"M8 2v3"}]],q3=[["path",{d:"M8 2v3"}],["path",{d:"M16 2v3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"m9 15 2 2 4-4"}]],U3=[["path",{d:"m11 21-3-3 3-3"}],["path",{d:"M16 2v3"}],["path",{d:"M21 9H3"}],["path",{d:"M3 11.5V5a2 2 0 012-2h14a2 2 0 012 2v14a2 2 0 01-2 2h-4"}],["path",{d:"m5 21-3-3 3-3"}],["path",{d:"M8 2v3"}]],O3=[["path",{d:"m13 21 3-3-3-3"}],["path",{d:"M16 2v3"}],["path",{d:"m19 21 3-3-3-3"}],["path",{d:"M21 11.5V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h4"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}]],G3=[["path",{d:"M16 14v2.2l1.6 1"}],["path",{d:"M16 2v3"}],["path",{d:"M21 7.338V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h2.338"}],["path",{d:"M3 9h5.859"}],["path",{d:"M8 2v3"}],["circle",{cx:"16",cy:"16",r:"6"}]],Z3=[["path",{d:"M8 2v3"}],["path",{d:"M16 2v3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"M8 13h.01"}],["path",{d:"M12 13h.01"}],["path",{d:"M16 13h.01"}],["path",{d:"M8 17h.01"}],["path",{d:"M12 17h.01"}],["path",{d:"M16 17h.01"}]],E3=[["path",{d:"m15.228 16.852-.923-.383"}],["path",{d:"m15.228 19.148-.923.383"}],["path",{d:"M16 2v3"}],["path",{d:"m16.47 14.305.382.923"}],["path",{d:"m16.852 20.772-.383.924"}],["path",{d:"m19.148 15.228.383-.923"}],["path",{d:"m19.53 21.696-.382-.924"}],["path",{d:"m20.773 16.852.924-.383"}],["path",{d:"m20.773 19.148.924.383"}],["path",{d:"M21 10.5V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h5.5"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}],["circle",{cx:"18",cy:"18",r:"3"}]],W3=[["path",{d:"M16 2v3"}],["path",{d:"M21 15V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h10v-5a1 1 0 011-1za2.4 2.4 0 01-.706 1.706l-3.588 3.588A2.4 2.4 0 0115 21"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}]],I3=[["path",{d:"M12.127 21H5a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v5.125"}],["path",{d:"M14.62 17.8A2.25 2.25 0 1118 14.836a2.25 2.25 0 113.38 2.966l-2.626 2.856a.998.998 0 01-1.507 0z"}],["path",{d:"M16 2v3"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}]],X3=[["path",{d:"M8 2v3"}],["path",{d:"M16 2v3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"M10 15h4"}]],N3=[["path",{d:"M16 18h6"}],["path",{d:"M16 2v3"}],["path",{d:"M21 14V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h8.3"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}]],K3=[["path",{d:"M16 2v3"}],["path",{d:"m2 2 20 20"}],["path",{d:"M21 9h-5.5"}],["path",{d:"M3 9h6"}],["path",{d:"M3.586 3.586A2 2 0 003 5v14a2 2 0 002 2h14a2 2 0 001.414-.586"}],["path",{d:"M8.656 3H19a2 2 0 012 2v10.344"}]],J3=[["path",{d:"M8 2v3"}],["path",{d:"M16 2v3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"M10 15h4"}],["path",{d:"M12 13v4"}]],Q3=[["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["path",{d:"M16 2v3"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}],["path",{d:"M17 13h-6"}],["path",{d:"M13 17H7"}],["path",{d:"M7 13h.01"}],["path",{d:"M17 17h.01"}]],j3=[["path",{d:"M16 2v3"}],["path",{d:"M21 10.69V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h7.25"}],["path",{d:"m22 21-1.875-1.875"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}],["circle",{cx:"18",cy:"17",r:"3"}]],Y3=[["path",{d:"M11 10v4h4"}],["path",{d:"m11 14 1.535-1.605a5 5 0 018 1.5"}],["path",{d:"M16 2v3"}],["path",{d:"m21 18-1.535 1.605a5 5 0 01-8-1.5"}],["path",{d:"M21 22v-4h-4"}],["path",{d:"M21 8.517V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h3.517"}],["path",{d:"M3 9h4"}],["path",{d:"M8 2v3"}]],_3=[["path",{d:"M16 18h6"}],["path",{d:"M16 2v3"}],["path",{d:"M19 15v6"}],["path",{d:"M21 11.5V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h8.3"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}]],x3=[["path",{d:"M16 2v3"}],["path",{d:"m17 16 5 5"}],["path",{d:"m17 21 5-5"}],["path",{d:"M21 12V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h8"}],["path",{d:"M3 9h18"}],["path",{d:"M8 2v3"}]],ad=[["path",{d:"M8 2v3"}],["path",{d:"M16 2v3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"m14 13-4 4"}],["path",{d:"m10 13 4 4"}]],hd=[["path",{d:"M8 2v3"}],["path",{d:"M16 2v3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["path",{d:"M3 9h18"}]],td=[["path",{d:"M12 2v2"}],["path",{d:"M15.726 21.01A2 2 0 0 1 14 22H4a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2"}],["path",{d:"M18 2v2"}],["path",{d:"M2 13h2"}],["path",{d:"M8 8h14"}],["rect",{x:"8",y:"3",width:"14",height:"14",rx:"2"}]],dd=[["path",{d:"M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"}],["circle",{cx:"12",cy:"13",r:"3"}]],cd=[["path",{d:"M14.564 14.558a3 3 0 1 1-4.122-4.121"}],["path",{d:"m2 2 20 20"}],["path",{d:"M20 20H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 .819-.175"}],["path",{d:"M9.695 4.024A2 2 0 0 1 10.004 4h3.993a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v7.344"}]],Md=[["path",{d:"m17 22 1.664-2.496a2 2 0 00.336-1.11V5.606a2 2 0 00-.336-1.11L17 2"}],["path",{d:"M18 22H6"}],["path",{d:"M18 2H6"}],["path",{d:"M5 17h14"}],["path",{d:"M5 7h14"}],["path",{d:"m7 22-1.664-2.496A2 2 0 015 18.394V5.606a2 2 0 01.336-1.11L7 2"}]],pd=[["path",{d:"M21 10.5a9 2.5 0 01-18 0v8a9 2.5 0 0018 0z"}],["path",{d:"M21 10.5A9 2.5 25.32 004.59 3.47 9 2.5 25.32 0021 10.5"}],["path",{d:"M3 10.5a9 2.5 0 016.527-2.405"}],["path",{d:"M9 16.858a31 31 0 006 0"}]],ld=[["path",{d:"m10.8 5 2.111 4.223"}],["path",{d:"M17.75 7 15 2.1"}],["path",{d:"m4.874 14.647 2.12 4.24"}],["path",{d:"M5.7 21a2 2 0 0 1-3.5-2l8.6-14a6 6 0 0 1 10.4 6 2 2 0 1 1-3.464-2 2 2 0 1 0-3.464-2z"}],["path",{d:"m7.906 9.712 2.005 4.411"}]],id=[["path",{d:"M10 10v7.9"}],["path",{d:"M11.802 6.145a5 5 0 0 1 6.053 6.053"}],["path",{d:"M14 6.1v2.243"}],["path",{d:"m15.5 15.571-.964.964a5 5 0 0 1-7.071 0 5 5 0 0 1 0-7.07l.964-.965"}],["path",{d:"M16 7V3a1 1 0 0 1 1.707-.707 2.5 2.5 0 0 0 2.152.717 1 1 0 0 1 1.131 1.131 2.5 2.5 0 0 0 .717 2.152A1 1 0 0 1 21 8h-4"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8 17v4a1 1 0 0 1-1.707.707 2.5 2.5 0 0 0-2.152-.717 1 1 0 0 1-1.131-1.131 2.5 2.5 0 0 0-.717-2.152A1 1 0 0 1 3 16h4"}]],nd=[["path",{d:"M10 7v10.9"}],["path",{d:"M14 6.1V17"}],["path",{d:"M16 7V3a1 1 0 0 1 1.707-.707 2.5 2.5 0 0 0 2.152.717 1 1 0 0 1 1.131 1.131 2.5 2.5 0 0 0 .717 2.152A1 1 0 0 1 21 8h-4"}],["path",{d:"M16.536 7.465a5 5 0 0 0-7.072 0l-2 2a5 5 0 0 0 0 7.07 5 5 0 0 0 7.072 0l2-2a5 5 0 0 0 0-7.07"}],["path",{d:"M8 17v4a1 1 0 0 1-1.707.707 2.5 2.5 0 0 0-2.152-.717 1 1 0 0 1-1.131-1.131 2.5 2.5 0 0 0-.717-2.152A1 1 0 0 1 3 16h4"}]],ed=[["path",{d:"M12 22v-4c1.5 1.5 3.5 3 6 3 0-1.5-.5-3.5-2-5"}],["path",{d:"M13.988 8.327C13.902 6.054 13.365 3.82 12 2a9.3 9.3 0 0 0-1.445 2.9"}],["path",{d:"M17.375 11.725C18.882 10.53 21 7.841 21 6c-2.324 0-5.08 1.296-6.662 2.684"}],["path",{d:"m2 2 20 20"}],["path",{d:"M21.024 15.378A15 15 0 0 0 22 15c-.426-1.279-2.67-2.557-4.25-2.907"}],["path",{d:"M6.995 6.992C5.714 6.4 4.29 6 3 6c0 2 2.5 5 4 6-1.5 0-4.5 1.5-5 3 3.5 1.5 6 1 6 1-1.5 1.5-2 3.5-2 5 2.5 0 4.5-1.5 6-3"}]],rd=[["path",{d:"M12 22v-4"}],["path",{d:"M7 12c-1.5 0-4.5 1.5-5 3 3.5 1.5 6 1 6 1-1.5 1.5-2 3.5-2 5 2.5 0 4.5-1.5 6-3 1.5 1.5 3.5 3 6 3 0-1.5-.5-3.5-2-5 0 0 2.5.5 6-1-.5-1.5-3.5-3-5-3 1.5-1 4-4 4-6-2.5 0-5.5 1.5-7 3 0-2.5-.5-5-2-7-1.5 2-2 4.5-2 7-1.5-1.5-4.5-3-7-3 0 2 2.5 5 4 6"}]],od=[["path",{d:"M10.5 5H19a2 2 0 0 1 2 2v8.5"}],["path",{d:"M17 11h-.5"}],["path",{d:"M19 19H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2"}],["path",{d:"m2 2 20 20"}],["path",{d:"M7 11h4"}],["path",{d:"M7 15h2.5"}]],T=[["rect",{width:"18",height:"14",x:"3",y:"5",rx:"2",ry:"2"}],["path",{d:"M7 15h4M15 15h2M7 11h2M13 11h4"}]],vd=[["path",{d:"M14 13h4"}],["path",{d:"M16 15v-4"}],["path",{d:"M18 5v2"}],["path",{d:"M6 13h4"}],["path",{d:"M6 5v2"}],["rect",{x:"2",y:"7",width:"20",height:"12",rx:"2"}]],$d=[["path",{d:"m21 8-2 2-1.5-3.7A2 2 0 0 0 15.646 5H8.4a2 2 0 0 0-1.903 1.257L5 10 3 8"}],["path",{d:"M7 14h.01"}],["path",{d:"M17 14h.01"}],["rect",{width:"18",height:"8",x:"3",y:"10",rx:"2"}],["path",{d:"M5 18v2"}],["path",{d:"M19 18v2"}]],md=[["path",{d:"M10 2h4"}],["path",{d:"m21 8-2 2-1.5-3.7A2 2 0 0 0 15.646 5H8.4a2 2 0 0 0-1.903 1.257L5 10 3 8"}],["path",{d:"M7 14h.01"}],["path",{d:"M17 14h.01"}],["rect",{width:"18",height:"8",x:"3",y:"10",rx:"2"}],["path",{d:"M5 18v2"}],["path",{d:"M19 18v2"}]],yd=[["path",{d:"M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2"}],["circle",{cx:"7",cy:"17",r:"2"}],["path",{d:"M9 17h6"}],["circle",{cx:"17",cy:"17",r:"2"}]],sd=[["path",{d:"M18 19V9a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v8a2 2 0 0 0 2 2h2"}],["path",{d:"M2 9h3a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2"}],["path",{d:"M22 17v1a1 1 0 0 1-1 1H10v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9"}],["circle",{cx:"8",cy:"19",r:"2"}]],Cd=[["path",{d:"M12 14v4"}],["path",{d:"M14.172 2a2 2 0 0 1 1.414.586l3.828 3.828A2 2 0 0 1 20 7.828V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z"}],["path",{d:"M8 14h8"}],["rect",{x:"8",y:"10",width:"8",height:"8",rx:"2"}]],ud=[["path",{d:"M15 16a1 1 0 0 0-7-7q-4 4-5.987 12.385a.5.5 0 0 0 .602.602Q11 20 15 16l-3-3"}],["path",{d:"M15 9q4 4 7 0-3-4-7 0 4-4 0-7-4 3 0 7"}],["path",{d:"m8 15-2.58-2.58"}]],gd=[["path",{d:"M10 10H5v10a2 2 0 002 2h10a2 2 0 002-2v-1"}],["path",{d:"M13 22v-9"}],["path",{d:"M13.902 8.245 16 6h-4.343"}],["path",{d:"M19 13.343V10a2 2 0 00-.539-1.367L16 6V3a1 1 0 00-1-1H9a1 1 0 00-.857.486"}],["path",{d:"m2 2 20 20"}],["path",{d:"M7.034 7.034 5.539 8.633A2 2 0 005 10"}]],Ad=[["path",{d:"M13 22V10a2 2 0 01.539-1.367L16 6H8L5.539 8.633A2 2 0 005 10v10a2 2 0 002 2h10a2 2 0 002-2V10a2 2 0 00-.539-1.367L16 6V3a1 1 0 00-1-1H9a1 1 0 00-1 1v3"}],["path",{d:"M5 10h8"}]],Hd=[["path",{d:"M10 9v7"}],["path",{d:"M14 6v10"}],["circle",{cx:"17.5",cy:"12.5",r:"3.5"}],["circle",{cx:"6.5",cy:"12.5",r:"3.5"}]],Vd=[["path",{d:"m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"}],["path",{d:"M22 9v7"}],["path",{d:"M3.304 13h6.392"}],["circle",{cx:"18.5",cy:"12.5",r:"3.5"}]],Sd=[["path",{d:"M15 11h4.5a1 1 0 0 1 0 5h-4a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h3a1 1 0 0 1 0 5"}],["path",{d:"m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"}],["path",{d:"M3.304 13h6.392"}]],wd=[["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2"}],["circle",{cx:"8",cy:"10",r:"2"}],["path",{d:"M8 12h8"}],["circle",{cx:"16",cy:"10",r:"2"}],["path",{d:"m6 20 .7-2.9A1.4 1.4 0 0 1 8.1 16h7.8a1.4 1.4 0 0 1 1.4 1l.7 3"}]],Ld=[["path",{d:"M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"}],["path",{d:"M2 12a9 9 0 0 1 8 8"}],["path",{d:"M2 16a5 5 0 0 1 4 4"}],["line",{x1:"2",x2:"2.01",y1:"20",y2:"20"}]],fd=[["path",{d:"M10 5V3"}],["path",{d:"M14 5V3"}],["path",{d:"M15 21v-3a3 3 0 0 0-6 0v3"}],["path",{d:"M18 3v8"}],["path",{d:"M18 5H6"}],["path",{d:"M22 11H2"}],["path",{d:"M22 9v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9"}],["path",{d:"M6 3v8"}]],kd=[["path",{d:"M12 5c.67 0 1.35.09 2 .26 1.78-2 5.03-2.84 6.42-2.26 1.4.58-.42 7-.42 7 .57 1.07 1 2.24 1 3.44C21 17.9 16.97 21 12 21s-9-3-9-7.56c0-1.25.5-2.4 1-3.44 0 0-1.89-6.42-.5-7 1.39-.58 4.72.23 6.5 2.23A9.04 9.04 0 0 1 12 5Z"}],["path",{d:"M8 14v.5"}],["path",{d:"M16 14v.5"}],["path",{d:"M11.25 16.25h1.5L12 17l-.75-.75Z"}]],Pd=[["path",{d:"m12.309 6.652 4.797 2.401a1 1 0 0 1 .447 1.341l-.501 1.001.605.605h2.725a1 1 0 0 1 .894 1.447l-.724 1.448"}],["path",{d:"m15.166 15.166-.719 1.439a1 1 0 0 1-1.342.447L3.61 12.3a2.92 2.92 0 0 1-1.3-3.91L3.69 5.6a2.9 2.9 0 0 1 .873-1.037"}],["path",{d:"M2 19h3.76a2 2 0 0 0 1.8-1.1l1.441-2.902"}],["path",{d:"m2 2 20 20"}],["path",{d:"M2 21v-4"}],["path",{d:"M7 9h.01"}]],Bd=[["path",{d:"M16.75 12h3.632a1 1 0 0 1 .894 1.447l-2.034 4.069a1 1 0 0 1-1.708.134l-2.124-2.97"}],["path",{d:"M17.106 9.053a1 1 0 0 1 .447 1.341l-3.106 6.211a1 1 0 0 1-1.342.447L3.61 12.3a2.92 2.92 0 0 1-1.3-3.91L3.69 5.6a2.92 2.92 0 0 1 3.92-1.3z"}],["path",{d:"M2 19h3.76a2 2 0 0 0 1.8-1.1L9 15"}],["path",{d:"M2 21v-4"}],["path",{d:"M7 9h.01"}]],zd=[["path",{d:"M12 10H6"}],["path",{d:"m20.69 15.479 1.29 4.88a.5.5 0 01-.698.591l-1.843-.849a1 1 0 00-.879.001l-1.846.85a.5.5 0 01-.692-.593l1.29-4.88"}],["path",{d:"M22 6.675V6a2 2 0 00-2-2H4a2 2 0 00-2 2v12a2 2 0 002 2h8"}],["path",{d:"M6 14h4"}],["circle",{cx:"19",cy:"13",r:"3"}]],q=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z"}]],U=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["rect",{x:"7",y:"13",width:"9",height:"4",rx:"1"}],["rect",{x:"7",y:"5",width:"12",height:"4",rx:"1"}]],Dd=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M7 11h8"}],["path",{d:"M7 16h12"}],["path",{d:"M7 6h3"}]],Fd=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M7 11h8"}],["path",{d:"M7 16h3"}],["path",{d:"M7 6h12"}]],Rd=[["path",{d:"M11 13v4"}],["path",{d:"M15 5v4"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["rect",{x:"7",y:"13",width:"9",height:"4",rx:"1"}],["rect",{x:"7",y:"5",width:"12",height:"4",rx:"1"}]],O=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M7 16h8"}],["path",{d:"M7 11h12"}],["path",{d:"M7 6h3"}]],G=[["path",{d:"M9 5v4"}],["rect",{width:"4",height:"6",x:"7",y:"9",rx:"1"}],["path",{d:"M9 15v2"}],["path",{d:"M17 3v2"}],["rect",{width:"4",height:"8",x:"15",y:"5",rx:"1"}],["path",{d:"M17 13v3"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}]],Z=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["rect",{x:"15",y:"5",width:"4",height:"12",rx:"1"}],["rect",{x:"7",y:"8",width:"4",height:"9",rx:"1"}]],bd=[["path",{d:"M13 17V9"}],["path",{d:"M18 17v-3"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M8 17V5"}]],E=[["path",{d:"M13 17V9"}],["path",{d:"M18 17V5"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M8 17v-3"}]],Td=[["path",{d:"M11 13H7"}],["path",{d:"M19 9h-4"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["rect",{x:"15",y:"5",width:"4",height:"12",rx:"1"}],["rect",{x:"7",y:"8",width:"4",height:"9",rx:"1"}]],W=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M18 17V9"}],["path",{d:"M13 17V5"}],["path",{d:"M8 17v-3"}]],qd=[["path",{d:"M10 6h8"}],["path",{d:"M12 16h6"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M8 11h7"}]],I=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"m19 9-5 5-4-4-3 3"}]],Ud=[["path",{d:"m13.11 7.664 1.78 2.672"}],["path",{d:"m14.162 12.788-3.324 1.424"}],["path",{d:"m20 4-6.06 1.515"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["circle",{cx:"12",cy:"6",r:"2"}],["circle",{cx:"16",cy:"12",r:"2"}],["circle",{cx:"9",cy:"15",r:"2"}]],X=[["path",{d:"M5 21v-6"}],["path",{d:"M12 21V9"}],["path",{d:"M19 21V3"}]],Od=[["path",{d:"M5 21V3"}],["path",{d:"M12 21V9"}],["path",{d:"M19 21v-6"}]],N=[["path",{d:"M5 21v-6"}],["path",{d:"M12 21V3"}],["path",{d:"M19 21V9"}]],Gd=[["path",{d:"M12 16v5"}],["path",{d:"M16 14.639V21"}],["path",{d:"M20 10.656V21"}],["path",{d:"m22 3-8.646 8.646a.5.5 0 0 1-.708 0L9.354 8.354a.5.5 0 0 0-.707 0L2 15"}],["path",{d:"M4 18.463V21"}],["path",{d:"M8 14.656V21"}]],K=[["path",{d:"M6 5h12"}],["path",{d:"M4 12h10"}],["path",{d:"M12 19h8"}]],J=[["path",{d:"M21 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z"}],["path",{d:"M21.21 15.89A10 10 0 1 1 8 2.83"}]],Q=[["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor"}],["circle",{cx:"18.5",cy:"5.5",r:".5",fill:"currentColor"}],["circle",{cx:"11.5",cy:"11.5",r:".5",fill:"currentColor"}],["circle",{cx:"7.5",cy:"16.5",r:".5",fill:"currentColor"}],["circle",{cx:"17.5",cy:"14.5",r:".5",fill:"currentColor"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}]],Zd=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{d:"M7 16c.5-2 1.5-7 4-7 2 0 2 3 4 3 2.5 0 4.5-5 5-7"}]],Ed=[["path",{d:"M18 6 7 17l-5-5"}],["path",{d:"m22 10-7.5 7.5L13 16"}]],Wd=[["path",{d:"M20 4L9 15"}],["path",{d:"M21 19L3 19"}],["path",{d:"M9 15L4 10"}]],Id=[["path",{d:"M20 6 9 17l-5-5"}]],Xd=[["path",{d:"M17 21a1 1 0 0 0 1-1v-5.35c0-.457.316-.844.727-1.041a4 4 0 0 0-2.134-7.589 5 5 0 0 0-9.186 0 4 4 0 0 0-2.134 7.588c.411.198.727.585.727 1.041V20a1 1 0 0 0 1 1Z"}],["path",{d:"M6 17h12"}]],Nd=[["path",{d:"M2 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z"}],["path",{d:"M12 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z"}],["path",{d:"M7 14c3.22-2.91 4.29-8.75 5-12 1.66 2.38 4.94 9 5 12"}],["path",{d:"M22 9c-4.29 0-7.14-2.33-10-7 5.71 0 10 4.67 10 7Z"}]],Kd=[["path",{d:"M4 20a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"}],["path",{d:"m6.7 18-1-1C4.35 15.682 3 14.09 3 12a5 5 0 0 1 4.95-5c1.584 0 2.7.455 4.05 1.818C13.35 7.455 14.466 7 16.05 7A5 5 0 0 1 21 12c0 2.082-1.359 3.673-2.7 5l-1 1"}],["path",{d:"M10 4h4"}],["path",{d:"M12 2v6.818"}]],Jd=[["path",{d:"M5 20a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z"}],["path",{d:"M15 18c1.5-.615 3-2.461 3-4.923C18 8.769 14.5 4.462 12 2 9.5 4.462 6 8.77 6 13.077 6 15.539 7.5 17.385 9 18"}],["path",{d:"m16 7-2.5 2.5"}],["path",{d:"M9 2h6"}]],Qd=[["path",{d:"M5 20a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z"}],["path",{d:"M16.5 18c1-2 2.5-5 2.5-9a7 7 0 0 0-7-7H6.635a1 1 0 0 0-.768 1.64L7 5l-2.32 5.802a2 2 0 0 0 .95 2.526l2.87 1.456"}],["path",{d:"m15 5 1.425-1.425"}],["path",{d:"m17 8 1.53-1.53"}],["path",{d:"M9.713 12.185 7 18"}]],jd=[["path",{d:"M5 20a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z"}],["path",{d:"m14.5 10 1.5 8"}],["path",{d:"M7 10h10"}],["path",{d:"m8 18 1.5-8"}],["circle",{cx:"12",cy:"6",r:"4"}]],Yd=[["path",{d:"m10.68 5.499-.996 4.982a.65.65 0 01-1.131.295L5.885 7.663"}],["path",{d:"m13.32 5.5.995 4.98a.65.65 0 001.131.296l2.668-3.113"}],["path",{d:"M20.428 8.952 17.5 18"}],["path",{d:"M4 20a2 2 0 012-2h12a2 2 0 012 2v1a1 1 0 01-1 1H5a1 1 0 01-1-1z"}],["path",{d:"M6.5 18 3.571 8.952"}],["circle",{cx:"12",cy:"4",r:"2"}],["circle",{cx:"20",cy:"7",r:"2"}],["circle",{cx:"4",cy:"7",r:"2"}]],_d=[["path",{d:"M5 20a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z"}],["path",{d:"M10 2v2"}],["path",{d:"M14 2v2"}],["path",{d:"m17 18-1-9"}],["path",{d:"M6 2v5a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2"}],["path",{d:"M6 4h12"}],["path",{d:"m7 18 1-9"}]],xd=[["path",{d:"m6 9 6 6 6-6"}]],a6=[["path",{d:"m17 18-6-6 6-6"}],["path",{d:"M7 6v12"}]],h6=[["path",{d:"m7 18 6-6-6-6"}],["path",{d:"M17 6v12"}]],t6=[["path",{d:"m15 18-6-6 6-6"}]],d6=[["path",{d:"m9 18 6-6-6-6"}]],c6=[["path",{d:"m18 15-6-6-6 6"}]],M6=[["path",{d:"m7 20 5-5 5 5"}],["path",{d:"m7 4 5 5 5-5"}]],p6=[["path",{d:"m7 6 5 5 5-5"}],["path",{d:"m7 13 5 5 5-5"}]],l6=[["path",{d:"M12 12h.01"}],["path",{d:"M16 12h.01"}],["path",{d:"m17 7 5 5-5 5"}],["path",{d:"m7 7-5 5 5 5"}],["path",{d:"M8 12h.01"}]],i6=[["path",{d:"m9 7-5 5 5 5"}],["path",{d:"m15 7 5 5-5 5"}]],n6=[["path",{d:"m11 17-5-5 5-5"}],["path",{d:"m18 17-5-5 5-5"}]],e6=[["path",{d:"m20 17-5-5 5-5"}],["path",{d:"m4 17 5-5-5-5"}]],r6=[["path",{d:"m6 17 5-5-5-5"}],["path",{d:"m13 17 5-5-5-5"}]],o6=[["path",{d:"m7 15 5 5 5-5"}],["path",{d:"m7 9 5-5 5 5"}]],v6=[["path",{d:"M10 9h4"}],["path",{d:"M12 7v5"}],["path",{d:"M14 21v-3a2 2 0 0 0-4 0v3"}],["path",{d:"m18 9 3.52 2.147a1 1 0 0 1 .48.854V19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-6.999a1 1 0 0 1 .48-.854L6 9"}],["path",{d:"M6 21V7a1 1 0 0 1 .376-.782l5-3.999a1 1 0 0 1 1.249.001l5 4A1 1 0 0 1 18 7v14"}]],$6=[["path",{d:"m17 11-5-5-5 5"}],["path",{d:"m17 18-5-5-5 5"}]],m6=[["path",{d:"M12 12H3a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h13"}],["path",{d:"M18 8c0-2.5-2-2.5-2-5"}],["path",{d:"m2 2 20 20"}],["path",{d:"M21 12a1 1 0 0 1 1 1v2a1 1 0 0 1-.5.866"}],["path",{d:"M22 8c0-2.5-2-2.5-2-5"}],["path",{d:"M7 12v4"}]],y6=[["path",{d:"M17 12H3a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h14"}],["path",{d:"M18 8c0-2.5-2-2.5-2-5"}],["path",{d:"M21 16a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1"}],["path",{d:"M22 8c0-2.5-2-2.5-2-5"}],["path",{d:"M7 12v4"}]],j=[["circle",{cx:"12",cy:"12",r:"10"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16"}]],Y=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m12 8-4 4 4 4"}],["path",{d:"M16 12H8"}]],_=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 8v8"}],["path",{d:"m8 12 4 4 4-4"}]],x=[["path",{d:"M2 12a10 10 0 1 1 10 10"}],["path",{d:"m2 22 10-10"}],["path",{d:"M8 22H2v-6"}]],a1=[["path",{d:"M12 22a10 10 0 1 1 10-10"}],["path",{d:"M22 22 12 12"}],["path",{d:"M22 16v6h-6"}]],h1=[["path",{d:"M2 8V2h6"}],["path",{d:"m2 2 10 10"}],["path",{d:"M12 2A10 10 0 1 1 2 12"}]],t1=[["path",{d:"M22 12A10 10 0 1 1 12 2"}],["path",{d:"M22 2 12 12"}],["path",{d:"M16 2h6v6"}]],d1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m12 16 4-4-4-4"}],["path",{d:"M8 12h8"}]],c1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m16 12-4-4-4 4"}],["path",{d:"M12 16V8"}]],M1=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335"}],["path",{d:"m9 11 3 3L22 4"}]],p1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m16 9-5.5 5.5L8 12"}]],l1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m16 10-4 4-4-4"}]],i1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m14 16-4-4 4-4"}]],n1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m10 8 4 4-4 4"}]],e1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m8 14 4-4 4 4"}]],s6=[["path",{d:"M9 14L12 17L15 14"}],["path",{d:"M9 8L12 11L15 8"}],["circle",{cx:"12",cy:"12",r:"10"}]],C6=[["path",{d:"M10 9L7 12L10 15"}],["path",{d:"M16 9L13 12L16 15"}],["circle",{cx:"12",cy:"12",r:"10"}]],u6=[["path",{d:"M14 15L17 12L14 9"}],["path",{d:"M8 15L11 12L8 9"}],["circle",{cx:"12",cy:"12",r:"10"}]],g6=[["path",{d:"M15 10L12 7L9 10"}],["path",{d:"M15 16L12 13L9 16"}],["circle",{cx:"12",cy:"12",r:"10"}]],A6=[["path",{d:"M10.1 2.182a10 10 0 013.8 0"}],["path",{d:"M13.9 21.818a10 10 0 01-3.8 0"}],["path",{d:"m16 9-5.5 5.5L8 12"}],["path",{d:"M17.609 3.721a10 10 0 012.69 2.7"}],["path",{d:"M2.182 13.9a10 10 0 010-3.8"}],["path",{d:"M20.279 17.609a10 10 0 01-2.7 2.69"}],["path",{d:"M21.818 10.1a10 10 0 010 3.8"}],["path",{d:"M3.721 6.391a10 10 0 012.7-2.69"}],["path",{d:"M6.391 20.279a10 10 0 01-2.69-2.7"}]],H6=[["path",{d:"M10.1 2.182a10 10 0 0 1 3.8 0"}],["path",{d:"M13.9 21.818a10 10 0 0 1-3.8 0"}],["path",{d:"M17.609 3.721a10 10 0 0 1 2.69 2.7"}],["path",{d:"M2.182 13.9a10 10 0 0 1 0-3.8"}],["path",{d:"M20.279 17.609a10 10 0 0 1-2.7 2.69"}],["path",{d:"M21.818 10.1a10 10 0 0 1 0 3.8"}],["path",{d:"M3.721 6.391a10 10 0 0 1 2.7-2.69"}],["path",{d:"M6.391 20.279a10 10 0 0 1-2.69-2.7"}]],r1=[["circle",{cx:"12",cy:"12",r:"10"}],["line",{x1:"8",x2:"16",y1:"12",y2:"12"}],["line",{x1:"12",x2:"12",y1:"16",y2:"16"}],["line",{x1:"12",x2:"12",y1:"8",y2:"8"}]],V6=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"}],["path",{d:"M12 18V6"}]],S6=[["path",{d:"M10.1 2.18a9.93 9.93 0 0 1 3.8 0"}],["path",{d:"M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7"}],["path",{d:"M21.82 10.1a9.93 9.93 0 0 1 0 3.8"}],["path",{d:"M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69"}],["path",{d:"M13.9 21.82a9.94 9.94 0 0 1-3.8 0"}],["path",{d:"M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7"}],["path",{d:"M2.18 13.9a9.93 9.93 0 0 1 0-3.8"}],["path",{d:"M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69"}],["circle",{cx:"12",cy:"12",r:"1"}]],w6=[["circle",{cx:"12",cy:"12",r:"1"}],["circle",{cx:"12",cy:"12",r:"10"}]],L6=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M17 12h.01"}],["path",{d:"M12 12h.01"}],["path",{d:"M7 12h.01"}]],f6=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M7 10h10"}],["path",{d:"M7 14h10"}]],k6=[["path",{d:"M15 9.4a4 4 0 1 0 0 5.2"}],["path",{d:"M7 12h5"}],["circle",{cx:"12",cy:"12",r:"10"}]],P6=[["path",{d:"M12 2a10 10 0 0 1 7.38 16.75"}],["path",{d:"m16 12-4-4-4 4"}],["path",{d:"M12 16V8"}],["path",{d:"M2.5 8.875a10 10 0 0 0-.5 3"}],["path",{d:"M2.83 16a10 10 0 0 0 2.43 3.4"}],["path",{d:"M4.636 5.235a10 10 0 0 1 .891-.857"}],["path",{d:"M8.644 21.42a10 10 0 0 0 7.631-.38"}]],B6=[["path",{d:"M12 2a10 10 0 0 1 7.38 16.75"}],["path",{d:"M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z"}],["path",{d:"M2.5 8.875a10 10 0 0 0-.5 3"}],["path",{d:"M2.83 16a10 10 0 0 0 2.43 3.4"}],["path",{d:"M4.636 5.235a10 10 0 0 1 .891-.857"}],["path",{d:"M8.644 21.42a10 10 0 0 0 7.631-.38"}]],z6=[["path",{d:"M12 2a10 10 0 0 1 7.38 16.75"}],["path",{d:"M12 8v8"}],["path",{d:"M16 12H8"}],["path",{d:"M2.5 8.875a10 10 0 0 0-.5 3"}],["path",{d:"M2.83 16a10 10 0 0 0 2.43 3.4"}],["path",{d:"M4.636 5.235a10 10 0 0 1 .891-.857"}],["path",{d:"M8.644 21.42a10 10 0 0 0 7.631-.38"}]],o1=[["path",{d:"M15.6 2.7a10 10 0 1 0 5.7 5.7"}],["circle",{cx:"12",cy:"12",r:"2"}],["path",{d:"M13.4 10.6 19 5"}]],v1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M8 12h8"}]],D6=[["path",{d:"m2 2 20 20"}],["path",{d:"M8.35 2.69A10 10 0 0 1 21.3 15.65"}],["path",{d:"M19.08 19.08A10 10 0 1 1 4.92 4.92"}]],$1=[["path",{d:"M12.656 7H13a3 3 0 0 1 2.984 3.307"}],["path",{d:"M13 13H9"}],["path",{d:"M19.071 19.071A1 1 0 0 1 4.93 4.93"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8.357 2.687a10 10 0 0 1 12.956 12.956"}],["path",{d:"M9 17V9"}]],m1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M9 17V7h4a3 3 0 0 1 0 6H9"}]],y1=[["circle",{cx:"12",cy:"12",r:"10"}],["line",{x1:"10",x2:"10",y1:"15",y2:"9"}],["line",{x1:"14",x2:"14",y1:"15",y2:"9"}]],s1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m15 9-6 6"}],["path",{d:"M9 9h.01"}],["path",{d:"M15 15h.01"}]],F6=[["circle",{cx:"12",cy:"19",r:"2"}],["circle",{cx:"12",cy:"5",r:"2"}],["circle",{cx:"16",cy:"12",r:"2"}],["circle",{cx:"20",cy:"19",r:"2"}],["circle",{cx:"4",cy:"19",r:"2"}],["circle",{cx:"8",cy:"12",r:"2"}]],C1=[["path",{d:"M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z"}],["circle",{cx:"12",cy:"12",r:"10"}]],u1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M8 12h8"}],["path",{d:"M12 8v8"}]],g1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 7v4"}],["path",{d:"M7.998 9.003a5 5 0 1 0 8-.005"}]],R6=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M10 16V9.5a1 1 0 0 1 5 0"}],["path",{d:"M8 12h4"}],["path",{d:"M8 16h7"}]],n=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"}],["path",{d:"M12 17h.01"}]],A1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M22 2 2 22"}]],b6=[["circle",{cx:"12",cy:"12",r:"10"}],["line",{x1:"9",x2:"15",y1:"15",y2:"9"}]],T6=[["circle",{cx:"12",cy:"12",r:"6"}]],q6=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M11.051 7.616a1 1 0 0 1 1.909.024l.737 1.452a1 1 0 0 0 .737.535l1.634.256a1 1 0 0 1 .588 1.806l-1.172 1.168a1 1 0 0 0-.282.866l.259 1.613a1 1 0 0 1-1.541 1.134l-1.465-.75a1 1 0 0 0-.912 0l-1.465.75a1 1 0 0 1-1.539-1.133l.258-1.613a1 1 0 0 0-.282-.867l-1.156-1.152a1 1 0 0 1 .572-1.822l1.633-.256a1 1 0 0 0 .737-.535z"}]],H1=[["circle",{cx:"12",cy:"12",r:"10"}],["rect",{x:"9",y:"9",width:"6",height:"6",rx:"1"}]],V1=[["path",{d:"M17.925 20.056a6 6 0 0 0-11.851.001"}],["circle",{cx:"12",cy:"11",r:"4"}],["circle",{cx:"12",cy:"12",r:"10"}]],S1=[["circle",{cx:"12",cy:"12",r:"10"}],["circle",{cx:"12",cy:"10",r:"3"}],["path",{d:"M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"}]],w1=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m15 9-6 6"}],["path",{d:"m9 9 6 6"}]],U6=[["circle",{cx:"12",cy:"12",r:"10"}]],O6=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M11 9h4a2 2 0 0 0 2-2V3"}],["circle",{cx:"9",cy:"9",r:"2"}],["path",{d:"M7 21v-4a2 2 0 0 1 2-2h4"}],["circle",{cx:"15",cy:"15",r:"2"}]],G6=[["path",{d:"M21.66 17.67a1.08 1.08 0 0 1-.04 1.6A12 12 0 0 1 4.73 2.38a1.1 1.1 0 0 1 1.61-.04z"}],["path",{d:"M19.65 15.66A8 8 0 0 1 8.35 4.34"}],["path",{d:"m14 10-5.5 5.5"}],["path",{d:"M14 17.85V10H6.15"}]],Z6=[["path",{d:"m12.296 3.464 3.02 3.956"}],["path",{d:"M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3z"}],["path",{d:"M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"}],["path",{d:"m6.18 5.276 3.1 3.899"}]],E6=[["path",{d:"M10 4v16"}],["path",{d:"M14 4.764a3 3 0 1 1-.152 4.327A4 4 0 0 1 10 12a4 4 0 0 1 3.848 2.909A3 3 0 1 1 14 19.236"}],["path",{d:"M6 4v16"}]],W6=[["path",{d:"M19 11h.01"}],["path",{d:"M19 6h.01"}],["path",{d:"M5 8c0-4 4-4 4-4 6 0 6 6 6 6 0 7-10 11-10 11"}],["circle",{cx:"7",cy:"8",r:"2"}]],I6=[["path",{d:"M10.586 21.414a2 2 0 0 0 3.378-1.791L11.036 4.377a2 2 0 1 1 3.378 1.037C12.414 7.414 7 8 7 13a5 5 0 0 0 5 5 5 4 0 0 0 5-4 3 3 0 0 0-3-3 3 2 0 0 0-3 2"}]],X6=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["path",{d:"m9 14 2 2 4-4"}]],N6=[["path",{d:"M16 14v2.2l1.6 1"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v.832"}],["path",{d:"M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2"}],["circle",{cx:"16",cy:"16",r:"6"}],["rect",{x:"8",y:"2",width:"8",height:"4",rx:"1"}]],K6=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1"}],["path",{d:"M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v4"}],["path",{d:"M21 14H11"}],["path",{d:"m15 10-4 4 4 4"}]],J6=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["path",{d:"M12 11h4"}],["path",{d:"M12 16h4"}],["path",{d:"M8 11h.01"}],["path",{d:"M8 16h.01"}]],Q6=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["path",{d:"M9 14h6"}]],j6=[["path",{d:"M11 14h10"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v1.344"}],["path",{d:"m17 18 4-4-4-4"}],["path",{d:"M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113"}],["rect",{x:"8",y:"2",width:"8",height:"4",rx:"1"}]],L1=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1"}],["path",{d:"M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-.5"}],["path",{d:"M16 4h2a2 2 0 0 1 1.73 1"}],["path",{d:"M8 18h1"}],["path",{d:"M21.378 12.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"}]],f1=[["path",{d:"M16 4h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21.34 15.664a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"}],["path",{d:"M8 22H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["rect",{x:"8",y:"2",width:"8",height:"4",rx:"1"}]],Y6=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["path",{d:"M9 14h6"}],["path",{d:"M12 17v-6"}]],_6=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["path",{d:"M9 12v-1h6v1"}],["path",{d:"M11 17h2"}],["path",{d:"M12 11v6"}]],x6=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["path",{d:"m14.5 11.5-5 5"}],["path",{d:"m9.5 11.5 5 5"}]],a8=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}]],h8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l2-4"}]],t8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l-4-2"}]],d8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l-2-4"}]],c8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6"}]],M8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l4-2"}]],p8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6h4"}]],l8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l4 2"}]],i8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l2 4"}]],n8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v10"}]],e8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l-2 4"}]],r8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l-4 2"}]],o8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6H8"}]],v8=[["path",{d:"M12 6v6l4 2"}],["path",{d:"M20 12v5"}],["path",{d:"M20 21h.01"}],["path",{d:"M21.25 8.2A10 10 0 1 0 16 21.16"}]],$8=[["path",{d:"M12 6v6l2 1"}],["path",{d:"M12.337 21.994a10 10 0 1 1 9.588-8.767"}],["path",{d:"m14 18 4 4 4-4"}],["path",{d:"M18 14v8"}]],m8=[["path",{d:"M12 6v6l1.5.8"}],["path",{d:"M12.338 21.994a10 10 0 1 1 9.587-8.767"}],["path",{d:"M14 18h8"}],["path",{d:"m18 22-4-4 4-4"}]],y8=[["path",{d:"M12 6v6l2 1"}],["path",{d:"M13.5 21.885A10 10 0 1 1 22 12"}],["path",{d:"M14 18h8"}],["path",{d:"m18 22 4-4-4-4"}]],s8=[["path",{d:"M12 6v6l1.56.78"}],["path",{d:"M13.227 21.925a10 10 0 1 1 8.767-9.588"}],["path",{d:"m14 18 4-4 4 4"}],["path",{d:"M18 22v-8"}]],C8=[["path",{d:"M21.95 13a10 10 0 1 0-8.685 8.92"}],["path",{d:"M12 6v6l4 2"}],["path",{d:"m16 19 2 2 4-4"}]],u8=[["path",{d:"M12 2a10 10 0 0 1 7.38 16.75"}],["path",{d:"M12 6v6l4 2"}],["path",{d:"M2.5 8.875a10 10 0 0 0-.5 3"}],["path",{d:"M2.83 16a10 10 0 0 0 2.43 3.4"}],["path",{d:"M4.636 5.235a10 10 0 0 1 .891-.857"}],["path",{d:"M8.644 21.42a10 10 0 0 0 7.631-.38"}]],g8=[["path",{d:"M12 6v6l3.644 1.822"}],["path",{d:"M16 19h6"}],["path",{d:"M19 16v6"}],["path",{d:"M21.92 13.267a10 10 0 1 0-8.653 8.653"}]],A8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 6v6l4 2"}]],H8=[["path",{d:"M10 9.17a3 3 0 1 0 0 5.66"}],["path",{d:"M17 9.17a3 3 0 1 0 0 5.66"}],["rect",{x:"2",y:"5",width:"20",height:"14",rx:"2"}]],V8=[["path",{d:"M12 12v4"}],["path",{d:"M12 20h.01"}],["path",{d:"M8.128 16.949A7 7 0 1 1 15.71 8h1.79a1 1 0 0 1 0 9h-1.642"}]],S8=[["path",{d:"M21 15.251A4.5 4.5 0 0 0 17.5 8h-1.79A7 7 0 1 0 3 13.607"}],["path",{d:"M7 11v4h4"}],["path",{d:"M8 19a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5 4.82 4.82 0 0 0-3.41 1.41L7 15"}]],w8=[["path",{d:"m17 15-5.5 5.5L9 18"}],["path",{d:"M5.516 16.07A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 3.501 7.327"}]],L8=[["path",{d:"m10.852 19.772-.383.924"}],["path",{d:"m13.148 14.228.383-.923"}],["path",{d:"M13.148 19.772a3 3 0 1 0-2.296-5.544l-.383-.923"}],["path",{d:"m13.53 20.696-.382-.924a3 3 0 1 1-2.296-5.544"}],["path",{d:"m14.772 15.852.923-.383"}],["path",{d:"m14.772 18.148.923.383"}],["path",{d:"M4.2 15.1a7 7 0 1 1 9.93-9.858A7 7 0 0 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.2"}],["path",{d:"m9.228 15.852-.923-.383"}],["path",{d:"m9.228 18.148-.923.383"}]],k1=[["path",{d:"M12 13v8l-4-4"}],["path",{d:"m12 21 4-4"}],["path",{d:"M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284"}]],f8=[["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"}],["path",{d:"M8 19v1"}],["path",{d:"M8 14v1"}],["path",{d:"M16 19v1"}],["path",{d:"M16 14v1"}],["path",{d:"M12 21v1"}],["path",{d:"M12 16v1"}]],k8=[["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"}],["path",{d:"M16 17H7"}],["path",{d:"M17 21H9"}]],P8=[["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"}],["path",{d:"M16 14v2"}],["path",{d:"M8 14v2"}],["path",{d:"M16 20h.01"}],["path",{d:"M8 20h.01"}],["path",{d:"M12 16v2"}],["path",{d:"M12 22h.01"}]],B8=[["path",{d:"M11 20v2"}],["path",{d:"M18.376 14.512a6 6 0 0 0 3.461-4.127c.148-.625-.659-.97-1.248-.714a4 4 0 0 1-5.259-5.26c.255-.589-.09-1.395-.716-1.248a6 6 0 0 0-4.594 5.36"}],["path",{d:"M3 20a5 5 0 1 1 8.9-4H13a3 3 0 0 1 2 5.24"}],["path",{d:"M7 19v2"}]],z8=[["path",{d:"M6 16.326A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 .5 8.973"}],["path",{d:"m13 12-3 5h4l-3 5"}]],D8=[["path",{d:"M13 16a3 3 0 0 1 0 6H7a5 5 0 1 1 4.9-6z"}],["path",{d:"M18.376 14.512a6 6 0 0 0 3.461-4.127c.148-.625-.659-.97-1.248-.714a4 4 0 0 1-5.259-5.26c.255-.589-.09-1.395-.716-1.248a6 6 0 0 0-4.594 5.36"}]],F8=[["path",{d:"M10.94 5.274A7 7 0 0 1 15.71 10h1.79a4.5 4.5 0 0 1 4.222 6.057"}],["path",{d:"M18.796 18.81A4.5 4.5 0 0 1 17.5 19H9A7 7 0 0 1 5.79 5.78"}],["path",{d:"m2 2 20 20"}]],R8=[["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"}],["path",{d:"m9.2 22 3-7"}],["path",{d:"m9 13-3 7"}],["path",{d:"m17 13-3 7"}]],b8=[["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"}],["path",{d:"M16 14v6"}],["path",{d:"M8 14v6"}],["path",{d:"M12 16v6"}]],T8=[["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"}],["path",{d:"M8 15h.01"}],["path",{d:"M8 19h.01"}],["path",{d:"M12 17h.01"}],["path",{d:"M12 21h.01"}],["path",{d:"M16 15h.01"}],["path",{d:"M16 19h.01"}]],q8=[["path",{d:"M12 2v2"}],["path",{d:"m4.93 4.93 1.41 1.41"}],["path",{d:"M20 12h2"}],["path",{d:"m19.07 4.93-1.41 1.41"}],["path",{d:"M15.947 12.65a4 4 0 0 0-5.925-4.128"}],["path",{d:"M13 22H7a5 5 0 1 1 4.9-6H13a3 3 0 0 1 0 6Z"}]],U8=[["path",{d:"M12 2v2"}],["path",{d:"m4.93 4.93 1.41 1.41"}],["path",{d:"M20 12h2"}],["path",{d:"m19.07 4.93-1.41 1.41"}],["path",{d:"M15.947 12.65a4 4 0 0 0-5.925-4.128"}],["path",{d:"M3 20a5 5 0 1 1 8.9-4H13a3 3 0 0 1 2 5.24"}],["path",{d:"M11 20v2"}],["path",{d:"M7 19v2"}]],O8=[["path",{d:"m17 18-1.535 1.605a5 5 0 0 1-8-1.5"}],["path",{d:"M17 22v-4h-4"}],["path",{d:"M20.996 15.251A4.5 4.5 0 0 0 17.495 8h-1.79a7 7 0 1 0-12.709 5.607"}],["path",{d:"M7 10v4h4"}],["path",{d:"m7 14 1.535-1.605a5 5 0 0 1 8 1.5"}]],P1=[["path",{d:"M12 13v8"}],["path",{d:"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"}],["path",{d:"m8 17 4-4 4 4"}]],G8=[["path",{d:"M17.5 12a1 1 0 1 1 0 9H9.006a7 7 0 1 1 6.702-9z"}],["path",{d:"M21.832 9A3 3 0 0 0 19 7h-2.207a5.5 5.5 0 0 0-10.72.61"}]],Z8=[["path",{d:"M16.17 7.83 2 22"}],["path",{d:"M4.02 12a2.827 2.827 0 1 1 3.81-4.17A2.827 2.827 0 1 1 12 4.02a2.827 2.827 0 1 1 4.17 3.81A2.827 2.827 0 1 1 19.98 12a2.827 2.827 0 1 1-3.81 4.17A2.827 2.827 0 1 1 12 19.98a2.827 2.827 0 1 1-4.17-3.81A1 1 0 1 1 4 12"}],["path",{d:"m7.83 7.83 8.34 8.34"}]],E8=[["path",{d:"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"}]],W8=[["path",{d:"M17.28 9.05a5.5 5.5 0 1 0-10.56 0A5.5 5.5 0 1 0 12 17.66a5.5 5.5 0 1 0 5.28-8.6Z"}],["path",{d:"M12 17.66L12 22"}]],B1=[["path",{d:"m18 16 4-4-4-4"}],["path",{d:"m6 8-4 4 4 4"}],["path",{d:"m14.5 4-5 16"}]],I8=[["path",{d:"m16 18 6-6-6-6"}],["path",{d:"m8 6-6 6 6 6"}]],X8=[["path",{d:"M10 2v2"}],["path",{d:"M14 2v2"}],["path",{d:"M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1"}],["path",{d:"M6 2v2"}]],N8=[["path",{d:"M11 10.27 7 3.34"}],["path",{d:"m11 13.73-4 6.93"}],["path",{d:"M12 22v-2"}],["path",{d:"M12 2v2"}],["path",{d:"M14 12h8"}],["path",{d:"m17 20.66-1-1.73"}],["path",{d:"m17 3.34-1 1.73"}],["path",{d:"M2 12h2"}],["path",{d:"m20.66 17-1.73-1"}],["path",{d:"m20.66 7-1.73 1"}],["path",{d:"m3.34 17 1.73-1"}],["path",{d:"m3.34 7 1.73 1"}],["circle",{cx:"12",cy:"12",r:"2"}],["circle",{cx:"12",cy:"12",r:"8"}]],K8=[["path",{d:"M13.744 17.736a6 6 0 1 1-7.48-7.48"}],["path",{d:"M15 6h1v4"}],["path",{d:"m6.134 14.768.866-.5 2 3.464"}],["circle",{cx:"16",cy:"8",r:"6"}]],z1=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M12 3v18"}]],e=[["path",{d:"M10.6 21H5a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v5.6"}],["path",{d:"m14.305 19.53.923-.382"}],["path",{d:"M15 3v7.6"}],["path",{d:"m15.229 16.852-.924-.383"}],["path",{d:"m16.852 15.228-.383-.923"}],["path",{d:"m16.852 20.772-.383.924"}],["path",{d:"m19.148 15.228.383-.923"}],["path",{d:"m19.53 21.696-.382-.924"}],["path",{d:"m20.773 16.852.922-.383"}],["path",{d:"m20.773 19.148.922.383"}],["path",{d:"M9 3v18"}],["circle",{cx:"18",cy:"18",r:"3"}]],D1=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M9 3v18"}],["path",{d:"M15 3v18"}]],J8=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M7.5 3v18"}],["path",{d:"M12 3v18"}],["path",{d:"M16.5 3v18"}]],Q8=[["path",{d:"M14 3a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1"}],["path",{d:"M19 3a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1"}],["path",{d:"m7 15 3 3"}],["path",{d:"m7 21 3-3H5a2 2 0 0 1-2-2v-2"}],["rect",{x:"14",y:"14",width:"7",height:"7",rx:"1"}],["rect",{x:"3",y:"3",width:"7",height:"7",rx:"1"}]],j8=[["path",{d:"M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3"}]],Y8=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"}]],_8=[["path",{d:"M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"}],["path",{d:"M2.297 11.293a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0z"}],["path",{d:"M8.916 17.912a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0z"}],["path",{d:"M8.916 4.674a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"}]],x8=[["path",{d:"M12 18h6"}],["path",{d:"M6 18h.01"}],["path",{d:"M8 6h1"}],["rect",{x:"2",y:"14",width:"20",height:"8",rx:"2"}],["rect",{x:"4",y:"2",width:"16",height:"12",rx:"2"}]],ac=[["path",{d:"m20.9 18.55-8-15.98a1 1 0 0 0-1.8 0l-8 15.98"}],["ellipse",{cx:"12",cy:"19",rx:"9",ry:"3"}]],hc=[["path",{d:"M3 20a1 1 0 0 1-1-1v-1a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1Z"}],["path",{d:"M20 16a8 8 0 1 0-16 0"}],["path",{d:"M12 4v4"}],["path",{d:"M10 4h4"}]],tc=[["rect",{x:"2",y:"6",width:"20",height:"8",rx:"1"}],["path",{d:"M17 14v7"}],["path",{d:"M7 14v7"}],["path",{d:"M17 3v3"}],["path",{d:"M7 3v3"}],["path",{d:"M10 14 2.3 6.3"}],["path",{d:"m14 6 7.7 7.7"}],["path",{d:"m8 6 8 8"}]],F1=[["path",{d:"M16 2v2"}],["path",{d:"M17.915 21a6 6 0 10-12 0"}],["path",{d:"M8 2v2"}],["circle",{cx:"12",cy:"11",r:"4"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],dc=[["path",{d:"M16 2v2"}],["path",{d:"M7 21v-2a2 2 0 012-2h6a2 2 0 012 2v2"}],["path",{d:"M8 2v2"}],["circle",{cx:"12",cy:"10",r:"3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],cc=[["path",{d:"M22 7.7c0-.6-.4-1.2-.8-1.5l-6.3-3.9a1.72 1.72 0 0 0-1.7 0l-10.3 6c-.5.2-.9.8-.9 1.4v6.6c0 .5.4 1.2.8 1.5l6.3 3.9a1.72 1.72 0 0 0 1.7 0l10.3-6c.5-.3.9-1 .9-1.5Z"}],["path",{d:"M10 21.9V14L2.1 9.1"}],["path",{d:"m10 14 11.9-6.9"}],["path",{d:"M14 19.8v-8.1"}],["path",{d:"M18 17.5V9.4"}]],Mc=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 18a6 6 0 0 0 0-12v12z"}]],pc=[["path",{d:"M11 17h.01"}],["path",{d:"M11.496 2c.324-.016.558.292.529.615a4 4 0 004.235 4.368.713.713 0 01.758.757 4 4 0 004.366 4.237c.323-.03.63.204.614.527a10 10 0 01-2.915 6.566A1 1 0 114.93 4.918 10 10 0 0111.496 2"}],["path",{d:"M12 12h.01"}],["path",{d:"M16 16h.01"}],["path",{d:"M16 3h.01"}],["path",{d:"M21 4h.01"}],["path",{d:"M21 8h.01"}],["path",{d:"M7 14h.01"}],["path",{d:"M9 8h.01"}]],lc=[["path",{d:"M2 12h20"}],["path",{d:"M20 12v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8"}],["path",{d:"m4 8 16-4"}],["path",{d:"m8.86 6.78-.45-1.81a2 2 0 0 1 1.45-2.43l1.94-.48a2 2 0 0 1 2.43 1.46l.45 1.8"}]],ic=[["path",{d:"m12 15 2 2 4-4"}],["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]],nc=[["line",{x1:"12",x2:"18",y1:"15",y2:"15"}],["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]],ec=[["line",{x1:"15",x2:"15",y1:"12",y2:"18"}],["line",{x1:"12",x2:"18",y1:"15",y2:"15"}],["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]],rc=[["line",{x1:"12",x2:"18",y1:"18",y2:"12"}],["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]],oc=[["path",{d:"M4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2"}],["rect",{x:"8",y:"8",width:"14",height:"14",rx:"2"}],["path",{d:"m12.5 12.5 5 5"}],["path",{d:"m12.5 17.5 5-5"}]],vc=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]],$c=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M9.17 14.83a4 4 0 1 0 0-5.66"}]],mc=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M14.83 14.83a4 4 0 1 1 0-5.66"}]],yc=[["path",{d:"M20 4v7a4 4 0 0 1-4 4H4"}],["path",{d:"m9 10-5 5 5 5"}]],sc=[["path",{d:"m15 10 5 5-5 5"}],["path",{d:"M4 4v7a4 4 0 0 0 4 4h12"}]],Cc=[["path",{d:"m14 15-5 5-5-5"}],["path",{d:"M20 4h-7a4 4 0 0 0-4 4v12"}]],uc=[["path",{d:"M14 9 9 4 4 9"}],["path",{d:"M20 20h-7a4 4 0 0 1-4-4V4"}]],gc=[["path",{d:"m10 15 5 5 5-5"}],["path",{d:"M4 4h7a4 4 0 0 1 4 4v12"}]],Ac=[["path",{d:"M20 20v-7a4 4 0 0 0-4-4H4"}],["path",{d:"M9 14 4 9l5-5"}]],Hc=[["path",{d:"m10 9 5-5 5 5"}],["path",{d:"M4 20h7a4 4 0 0 0 4-4V4"}]],Vc=[["path",{d:"m15 14 5-5-5-5"}],["path",{d:"M4 20v-7a4 4 0 0 1 4-4h12"}]],Sc=[["path",{d:"M12 20v2"}],["path",{d:"M12 2v2"}],["path",{d:"M16 9v6"}],["path",{d:"M17 20v2"}],["path",{d:"M17 2v2"}],["path",{d:"M2 12h2"}],["path",{d:"M2 17h2"}],["path",{d:"M2 7h2"}],["path",{d:"M20 12h2"}],["path",{d:"M20 17h2"}],["path",{d:"M20 7h2"}],["path",{d:"M7 20v2"}],["path",{d:"M7 2v2"}],["path",{d:"M8 13h4"}],["path",{d:"M8 15v-4a2 2 0 014 0v4"}],["rect",{x:"4",y:"4",width:"16",height:"16",rx:"2"}]],wc=[["path",{d:"M12 20v2"}],["path",{d:"M12 2v2"}],["path",{d:"M17 20v2"}],["path",{d:"M17 2v2"}],["path",{d:"M2 12h2"}],["path",{d:"M2 17h2"}],["path",{d:"M2 7h2"}],["path",{d:"M20 12h2"}],["path",{d:"M20 17h2"}],["path",{d:"M20 7h2"}],["path",{d:"M7 20v2"}],["path",{d:"M7 2v2"}],["rect",{x:"4",y:"4",width:"16",height:"16",rx:"2"}],["rect",{x:"8",y:"8",width:"8",height:"8",rx:"1"}]],Lc=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M10 9.3a2.8 2.8 0 0 0-3.5 1 3.1 3.1 0 0 0 0 3.4 2.7 2.7 0 0 0 3.5 1"}],["path",{d:"M17 9.3a2.8 2.8 0 0 0-3.5 1 3.1 3.1 0 0 0 0 3.4 2.7 2.7 0 0 0 3.5 1"}]],fc=[["path",{d:"M12.5 19H4a2 2 0 01-2-2V7a2 2 0 012-2h16a2 2 0 012 2v4"}],["path",{d:"M2 10h20"}],["path",{d:"M6 14h2"}],["path",{d:"m16 17 2 2 4-4"}]],kc=[["path",{d:"M22 13V7a2 2 0 00-2-2H4a2 2 0 00-2 2v10a2 2 0 002 2h8.536"}],["path",{d:"M22 10H2"}],["path",{d:"M6 14h2"}],["path",{d:"M16 17h6"}]],Pc=[["path",{d:"M22 11.354V7a2 2 0 00-2-2H4a2 2 0 00-2 2v10a2 2 0 002 2h8.536"}],["path",{d:"M22 10H2"}],["path",{d:"M6 14h2"}],["path",{d:"M16 17h6"}],["path",{d:"M19 14v6"}]],Bc=[["path",{d:"M15 16v1"}],["path",{d:"M16.963 7.734A1 1 0 0015.999 7H8.003a1 1 0 00-.964.734L4.073 18.467A2 2 0 006 21h12a2 2 0 001.927-2.532z"}],["path",{d:"M2.678 8.5A2 2 0 012 7V5a2 2 0 012-2h16a2 2 0 012 2v2a2 2 0 01-.676 1.499"}],["path",{d:"m9 21 2-14"}]],zc=[["path",{d:"M12.5 19H4a2 2 0 01-2-2V7a2 2 0 012-2h16a2 2 0 012 2v3.5"}],["path",{d:"M2 10h20"}],["path",{d:"M6 14h2"}],["path",{d:"m16.5 14.5 5 5"}],["path",{d:"m21.5 14.5-5 5"}]],Dc=[["rect",{width:"20",height:"14",x:"2",y:"5",rx:"2"}],["line",{x1:"2",x2:"22",y1:"10",y2:"10"}],["path",{d:"M6 14h2"}]],Fc=[["path",{d:"M10.2 18H4.774a1.5 1.5 0 0 1-1.352-.97 11 11 0 0 1 .132-6.487"}],["path",{d:"M18 10.2V4.774a1.5 1.5 0 0 0-.97-1.352 11 11 0 0 0-6.486.132"}],["path",{d:"M18 5a4 3 0 0 1 4 3 2 2 0 0 1-2 2 10 10 0 0 0-5.139 1.42"}],["path",{d:"M5 18a3 4 0 0 0 3 4 2 2 0 0 0 2-2 10 10 0 0 1 1.42-5.14"}],["path",{d:"M8.709 2.554a10 10 0 0 0-6.155 6.155 1.5 1.5 0 0 0 .676 1.626l9.807 5.42a2 2 0 0 0 2.718-2.718l-5.42-9.807a1.5 1.5 0 0 0-1.626-.676"}]],Rc=[["path",{d:"M6 2v14a2 2 0 0 0 2 2h14"}],["path",{d:"M18 22V8a2 2 0 0 0-2-2H2"}]],bc=[["path",{d:"M4 9a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h4a1 1 0 0 1 1 1v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-4a1 1 0 0 1 1-1h4a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-4a1 1 0 0 1-1-1V4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4a1 1 0 0 1-1 1z"}]],Tc=[["circle",{cx:"12",cy:"12",r:"10"}],["line",{x1:"22",x2:"18",y1:"12",y2:"12"}],["line",{x1:"6",x2:"2",y1:"12",y2:"12"}],["line",{x1:"12",x2:"12",y1:"6",y2:"2"}],["line",{x1:"12",x2:"12",y1:"22",y2:"18"}]],qc=[["path",{d:"M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z"}],["path",{d:"M5 21h14"}]],Uc=[["path",{d:"M10 22v-8"}],["path",{d:"M2.336 8.89 10 14l11.715-7.029"}],["path",{d:"M22 14a2 2 0 0 1-.971 1.715l-10 6a2 2 0 0 1-2.138-.05l-6-4A2 2 0 0 1 2 16v-6a2 2 0 0 1 .971-1.715l10-6a2 2 0 0 1 2.138.05l6 4A2 2 0 0 1 22 8z"}]],Oc=[["path",{d:"M12 22v-9"}],["path",{d:"M14 4h1a3 3 0 013 3l-.004.125A4 4 0 0121 11v2"}],["path",{d:"m15.5 22 1.5-9"}],["path",{d:"M21 13a1 1 0 01.919 1.394l-2.74 6.394A2 2 0 0117.34 22H6.659a2 2 0 01-1.838-1.212l-2.74-6.394A1 1 0 013 13z"}],["path",{d:"M3 13v-2a4 4 0 013.003-3.875L6 7a3 3 0 013-3h1"}],["path",{d:"M8.5 22 7 13"}],["circle",{cx:"12",cy:"4",r:"2"}]],Gc=[["path",{d:"m6 8 1.75 12.28a2 2 0 0 0 2 1.72h4.54a2 2 0 0 0 2-1.72L18 8"}],["path",{d:"M5 8h14"}],["path",{d:"M7 15a6.47 6.47 0 0 1 5 0 6.47 6.47 0 0 0 5 0"}],["path",{d:"m12 8 1-6h2"}]],Zc=[["circle",{cx:"12",cy:"12",r:"8"}],["line",{x1:"3",x2:"6",y1:"3",y2:"6"}],["line",{x1:"21",x2:"18",y1:"3",y2:"6"}],["line",{x1:"3",x2:"6",y1:"21",y2:"18"}],["line",{x1:"21",x2:"18",y1:"21",y2:"18"}]],Ec=[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}],["path",{d:"M3 5v14a9 3 0 0 0 18 0V5"}]],Wc=[["path",{d:"M11 11.31c1.17.56 1.54 1.69 3.5 1.69 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"}],["path",{d:"M11.75 18c.35.5 1.45 1 2.75 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"}],["path",{d:"M2 10h4"}],["path",{d:"M2 14h4"}],["path",{d:"M2 18h4"}],["path",{d:"M2 6h4"}],["path",{d:"M7 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1L10 4a1 1 0 0 0-1-1z"}]],Ic=[["path",{d:"m16 19 3 3 3-3"}],["path",{d:"M19 16v6"}],["path",{d:"M21 12.536V5"}],["path",{d:"M3 12A9 3 0 0 0 15.182 14.806"}],["path",{d:"M3 5V19A9 3 0 0 0 13.318 21.968"}],["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}]],Xc=[["path",{d:"M19 22v-6"}],["path",{d:"M21 12.536V5"}],["path",{d:"m22 19-3-3-3 3"}],["path",{d:"M3 12A9 3 0 0 0 14.457 14.886"}],["path",{d:"M3 5V19A9 3 0 0 0 13.318 21.968"}],["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}]],Nc=[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}],["path",{d:"M3 12a9 3 0 0 0 5 2.69"}],["path",{d:"M21 9.3V5"}],["path",{d:"M3 5v14a9 3 0 0 0 6.47 2.88"}],["path",{d:"M12 12v4h4"}],["path",{d:"M13 20a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5c-1.33 0-2.54.54-3.41 1.41L12 16"}]],Kc=[["path",{d:"m16 19 2 2 4-4"}],["path",{d:"M21 13.127V5"}],["path",{d:"M3 12A9 3 0 0 0 21 12"}],["path",{d:"M3 5V19A9 3 0 0 0 13.318 21.968"}],["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}]],Jc=[["path",{d:"M19 16v6"}],["path",{d:"M21 12.536V5"}],["path",{d:"M22 19h-6"}],["path",{d:"M3 12A9 3 0 0 0 15.1824 14.8061"}],["path",{d:"M3 5V19A9 3 0 0 0 13.318 21.968"}],["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}]],Qc=[["path",{d:"M21 15V5"}],["path",{d:"M22 19h-6"}],["path",{d:"M3 12A9 3 0 0 0 21 12"}],["path",{d:"M3 5V19A9 3 0 0 0 13.318 21.968"}],["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}]],jc=[["path",{d:"M21 11.693V5"}],["path",{d:"m22 22-1.875-1.875"}],["path",{d:"M3 12a9 3 0 0 0 8.697 2.998"}],["path",{d:"M3 5v14a9 3 0 0 0 9.28 2.999"}],["circle",{cx:"18",cy:"18",r:"3"}],["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}]],Yc=[["path",{d:"m17 17 5 5"}],["path",{d:"M19.323 13.744A9 3 0 0 0 21 12"}],["path",{d:"M21 13.127V5"}],["path",{d:"m22 17-5 5"}],["path",{d:"M3 12A9 3 0 0 0 13.563 14.954"}],["path",{d:"M3 5V19A9 3 0 0 0 13 21.981"}],["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}]],_c=[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}],["path",{d:"M3 5V19A9 3 0 0 0 15 21.84"}],["path",{d:"M21 5V8"}],["path",{d:"M21 12L18 17H22L19 22"}],["path",{d:"M3 12A9 3 0 0 0 14.59 14.87"}]],xc=[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5"}],["path",{d:"M3 12A9 3 0 0 0 21 12"}]],a7=[["path",{d:"m13 21-3-3 3-3"}],["path",{d:"M20 18H10"}],["path",{d:"M3 11h.01"}],["rect",{x:"6",y:"3",width:"5",height:"8",rx:"2.5"}]],h7=[["path",{d:"M10 18h10"}],["path",{d:"m17 21 3-3-3-3"}],["path",{d:"M3 11h.01"}],["rect",{x:"15",y:"3",width:"5",height:"8",rx:"2.5"}],["rect",{x:"6",y:"3",width:"5",height:"8",rx:"2.5"}]],t7=[["path",{d:"M10.162 3.167A10 10 0 0 0 2 13a2 2 0 0 0 4 0v-1a2 2 0 0 1 4 0v4a2 2 0 0 0 4 0v-4a2 2 0 0 1 4 0v1a2 2 0 0 0 4-.006 10 10 0 0 0-8.161-9.826"}],["path",{d:"M20.804 14.869a9 9 0 0 1-17.608 0"}],["circle",{cx:"12",cy:"4",r:"2"}]],d7=[["path",{d:"M10 5a2 2 0 0 0-1.344.519l-6.328 5.74a1 1 0 0 0 0 1.481l6.328 5.741A2 2 0 0 0 10 19h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2z"}],["path",{d:"m12 9 6 6"}],["path",{d:"m18 9-6 6"}]],c7=[["circle",{cx:"19",cy:"19",r:"2"}],["circle",{cx:"5",cy:"5",r:"2"}],["path",{d:"M6.48 3.66a10 10 0 0 1 13.86 13.86"}],["path",{d:"m6.41 6.41 11.18 11.18"}],["path",{d:"M3.66 6.48a10 10 0 0 0 13.86 13.86"}]],M7=[["path",{d:"M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41L13.7 2.71a2.41 2.41 0 0 0-3.41 0z"}],["path",{d:"M8 12h8"}]],R1=[["path",{d:"M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41L13.7 2.71a2.41 2.41 0 0 0-3.41 0Z"}],["path",{d:"M9.2 9.2h.01"}],["path",{d:"m14.5 9.5-5 5"}],["path",{d:"M14.7 14.8h.01"}]],p7=[["path",{d:"M12 8v8"}],["path",{d:"M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41L13.7 2.71a2.41 2.41 0 0 0-3.41 0z"}],["path",{d:"M8 12h8"}]],l7=[["path",{d:"M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41l-7.59-7.59a2.41 2.41 0 0 0-3.41 0Z"}]],i7=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["path",{d:"M12 12h.01"}]],n7=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["path",{d:"M15 9h.01"}],["path",{d:"M9 15h.01"}]],e7=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["path",{d:"M16 8h.01"}],["path",{d:"M12 12h.01"}],["path",{d:"M8 16h.01"}]],r7=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["path",{d:"M16 8h.01"}],["path",{d:"M8 8h.01"}],["path",{d:"M8 16h.01"}],["path",{d:"M16 16h.01"}]],o7=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["path",{d:"M16 8h.01"}],["path",{d:"M16 12h.01"}],["path",{d:"M16 16h.01"}],["path",{d:"M8 8h.01"}],["path",{d:"M8 12h.01"}],["path",{d:"M8 16h.01"}]],v7=[["rect",{width:"12",height:"12",x:"2",y:"10",rx:"2",ry:"2"}],["path",{d:"m17.92 14 3.5-3.5a2.24 2.24 0 0 0 0-3l-5-4.92a2.24 2.24 0 0 0-3 0L10 6"}],["path",{d:"M6 18h.01"}],["path",{d:"M10 14h.01"}],["path",{d:"M15 6h.01"}],["path",{d:"M18 9h.01"}]],$7=[["path",{d:"M12 3v14"}],["path",{d:"M5 10h14"}],["path",{d:"M5 21h14"}]],m7=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["path",{d:"M16 8h.01"}],["path",{d:"M8 8h.01"}],["path",{d:"M8 16h.01"}],["path",{d:"M16 16h.01"}],["path",{d:"M12 12h.01"}]],y7=[["circle",{cx:"12",cy:"12",r:"10"}],["circle",{cx:"12",cy:"12",r:"4"}],["path",{d:"M12 12h.01"}]],s7=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M6 12c0-1.7.7-3.2 1.8-4.2"}],["circle",{cx:"12",cy:"12",r:"2"}],["path",{d:"M18 12c0 1.7-.7 3.2-1.8 4.2"}]],C7=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["circle",{cx:"12",cy:"12",r:"5"}],["path",{d:"M12 12h.01"}]],u7=[["circle",{cx:"12",cy:"12",r:"10"}],["circle",{cx:"12",cy:"12",r:"2"}]],g7=[["circle",{cx:"12",cy:"6",r:"1"}],["line",{x1:"5",x2:"19",y1:"12",y2:"12"}],["circle",{cx:"12",cy:"18",r:"1"}]],A7=[["path",{d:"M15 2c-1.35 1.5-2.092 3-2.5 4.5L14 8"}],["path",{d:"m17 6-2.891-2.891"}],["path",{d:"M2 15c3.333-3 6.667-3 10-3"}],["path",{d:"m2 2 20 20"}],["path",{d:"m20 9 .891.891"}],["path",{d:"M22 9c-1.5 1.35-3 2.092-4.5 2.5l-1-1"}],["path",{d:"M3.109 14.109 4 15"}],["path",{d:"m6.5 12.5 1 1"}],["path",{d:"m7 18 2.891 2.891"}],["path",{d:"M9 22c1.35-1.5 2.092-3 2.5-4.5L10 16"}]],H7=[["path",{d:"m10 16 1.5 1.5"}],["path",{d:"m14 8-1.5-1.5"}],["path",{d:"M15 2c-1.798 1.998-2.518 3.995-2.807 5.993"}],["path",{d:"m16.5 10.5 1 1"}],["path",{d:"m17 6-2.891-2.891"}],["path",{d:"M2 15c6.667-6 13.333 0 20-6"}],["path",{d:"m20 9 .891.891"}],["path",{d:"M3.109 14.109 4 15"}],["path",{d:"m6.5 12.5 1 1"}],["path",{d:"m7 18 2.891 2.891"}],["path",{d:"M9 22c1.798-1.998 2.518-3.995 2.807-5.993"}]],V7=[["path",{d:"M2 8h20"}],["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2"}],["path",{d:"M6 16h12"}]],S7=[["path",{d:"M11.25 16.25h1.5L12 17z"}],["path",{d:"M16 14v.5"}],["path",{d:"M4.42 11.247A13.152 13.152 0 0 0 4 14.556C4 18.728 7.582 21 12 21s8-2.272 8-6.444a11.702 11.702 0 0 0-.493-3.309"}],["path",{d:"M8 14v.5"}],["path",{d:"M8.5 8.5c-.384 1.05-1.083 2.028-2.344 2.5-1.931.722-3.576-.297-3.656-1-.113-.994 1.177-6.53 4-7 1.923-.321 3.651.845 3.651 2.235A7.497 7.497 0 0 1 14 5.277c0-1.39 1.844-2.598 3.767-2.277 2.823.47 4.113 6.006 4 7-.08.703-1.725 1.722-3.656 1-1.261-.472-1.855-1.45-2.239-2.5"}]],w7=[["line",{x1:"12",x2:"12",y1:"2",y2:"22"}],["path",{d:"M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"}]],L7=[["path",{d:"M10 21v-3a2 2 0 014 0v3"}],["path",{d:"M12 2v2"}],["path",{d:"M18 12v9"}],["path",{d:"M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2v-6a1 1 0 011-1h18a1 1 0 011 1z"}],["path",{d:"M4 12a8 8 0 0116 0"}],["path",{d:"M6 12v9"}]],f7=[["path",{d:"M20.5 10a2.5 2.5 0 0 1-2.4-3H18a2.95 2.95 0 0 1-2.6-4.4 10 10 0 1 0 6.3 7.1c-.3.2-.8.3-1.2.3"}],["circle",{cx:"12",cy:"12",r:"3"}]],k7=[["path",{d:"m14.305 19.53.923-.382"}],["path",{d:"m15.229 16.852-.924-.383"}],["path",{d:"m16.852 15.228-.383-.923"}],["path",{d:"m16.852 20.773-.383.924"}],["path",{d:"M19 10.35V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16"}],["path",{d:"m19.148 15.228.383-.923"}],["path",{d:"m19.53 21.697-.382-.924"}],["path",{d:"M2 21h8.58"}],["path",{d:"m20.773 16.852.922-.383"}],["path",{d:"m20.773 19.148.922.383"}],["path",{d:"M9 12h.01"}],["circle",{cx:"18",cy:"18",r:"3"}]],P7=[["path",{d:"M19 8V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16"}],["path",{d:"M2 21h8"}],["path",{d:"M20 16v-2a2 2 0 00-4 0v2"}],["path",{d:"M9 12h.01"}],["rect",{x:"14",y:"16",width:"8",height:"5",rx:"1"}]],B7=[["path",{d:"M18 13v3"}],["path",{d:"M19 9V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16"}],["path",{d:"M2 21h8"}],["path",{d:"M9 12h.01"}],["rect",{x:"14",y:"13",width:"8",height:"8",rx:"1"}]],z7=[["path",{d:"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16"}],["path",{d:"M2 21h20"}],["path",{d:"M9 12h.01"}]],D7=[["path",{d:"M10 21H2"}],["path",{d:"M10 3H7a2 2 0 00-2 2v16"}],["path",{d:"M14 12h.01"}],["path",{d:"M19 21V5a2 2 0 00-1.675-1.974l-6.163-1.013A1 1 0 0010 3v18a1 1 0 001.124.992z"}],["path",{d:"M22 21h-3"}]],F7=[["path",{d:"M12 17v-3a1 1 0 011-1h6"}],["path",{d:"M19 17h-9a1 1 0 00-1 1v3"}],["path",{d:"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16"}],["path",{d:"M19 9h-3a1 1 0 00-1 1v3"}],["path",{d:"M22 21H2"}]],R7=[["circle",{cx:"12",cy:"12",r:"1"}]],b7=[["path",{d:"M12 15V3"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}],["path",{d:"m7 10 5 5 5-5"}]],T7=[["path",{d:"m12.99 6.74 1.93 3.44"}],["path",{d:"M19.136 12a10 10 0 0 1-14.271 0"}],["path",{d:"m21 21-2.16-3.84"}],["path",{d:"m3 21 8.02-14.26"}],["circle",{cx:"12",cy:"5",r:"2"}]],q7=[["path",{d:"M10 11h.01"}],["path",{d:"M14 6h.01"}],["path",{d:"M18 6h.01"}],["path",{d:"M6.5 13.1h.01"}],["path",{d:"M22 5c0 9-4 12-6 12s-6-3-6-12c0-2 2-3 6-3s6 1 6 3"}],["path",{d:"M17.4 9.9c-.8.8-2 .8-2.8 0"}],["path",{d:"M10.1 7.1C9 7.2 7.7 7.7 6 8.6c-3.5 2-4.7 3.9-3.7 5.6 4.5 7.8 9.5 8.4 11.2 7.4.9-.5 1.9-2.1 1.9-4.7"}],["path",{d:"M9.1 16.5c.3-1.1 1.4-1.7 2.4-1.4"}]],U7=[["path",{d:"M10 18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a3 3 0 0 1-3-3 1 1 0 0 1 1-1z"}],["path",{d:"M13 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1l-.81 3.242a1 1 0 0 1-.97.758H8"}],["path",{d:"M14 4h3a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-3"}],["path",{d:"M18 6h4"}],["path",{d:"m5 10-2 8"}],["path",{d:"m7 18 2-8"}]],O7=[["path",{d:"M10 10 7 7"}],["path",{d:"m10 14-3 3"}],["path",{d:"m14 10 3-3"}],["path",{d:"m14 14 3 3"}],["path",{d:"M14.205 4.139a4 4 0 1 1 5.439 5.863"}],["path",{d:"M19.637 14a4 4 0 1 1-5.432 5.868"}],["path",{d:"M4.367 10a4 4 0 1 1 5.438-5.862"}],["path",{d:"M9.795 19.862a4 4 0 1 1-5.429-5.873"}],["rect",{x:"10",y:"8",width:"4",height:"8",rx:"1"}]],G7=[["path",{d:"M18.715 13.186C18.29 11.858 17.384 10.607 16 9.5c-2-1.6-3.5-4-4-6.5a10.7 10.7 0 0 1-.884 2.586"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8.795 8.797A11 11 0 0 1 8 9.5C6 11.1 5 13 5 15a7 7 0 0 0 13.222 3.208"}]],Z7=[["path",{d:"M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z"}]],E7=[["path",{d:"M7 16.3c2.2 0 4-1.83 4-4.05 0-1.16-.57-2.26-1.71-3.19S7.29 6.75 7 5.3c-.29 1.45-1.14 2.84-2.29 3.76S3 11.1 3 12.25c0 2.22 1.8 4.05 4 4.05z"}],["path",{d:"M12.56 6.6A10.97 10.97 0 0 0 14 3.02c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a6.98 6.98 0 0 1-11.91 4.97"}]],W7=[["path",{d:"m2 2 8 8"}],["path",{d:"m22 2-8 8"}],["ellipse",{cx:"12",cy:"9",rx:"10",ry:"5"}],["path",{d:"M7 13.4v7.9"}],["path",{d:"M12 14v8"}],["path",{d:"M17 13.4v7.9"}],["path",{d:"M2 9v8a10 5 0 0 0 20 0V9"}]],I7=[["path",{d:"M15.4 15.63a7.875 6 135 1 1 6.23-6.23 4.5 3.43 135 0 0-6.23 6.23"}],["path",{d:"m8.29 12.71-2.6 2.6a2.5 2.5 0 1 0-1.65 4.65A2.5 2.5 0 1 0 8.7 18.3l2.59-2.59"}]],X7=[["path",{d:"M17.596 12.768a2 2 0 1 0 2.829-2.829l-1.768-1.767a2 2 0 0 0 2.828-2.829l-2.828-2.828a2 2 0 0 0-2.829 2.828l-1.767-1.768a2 2 0 1 0-2.829 2.829z"}],["path",{d:"m2.5 21.5 1.4-1.4"}],["path",{d:"m20.1 3.9 1.4-1.4"}],["path",{d:"M5.343 21.485a2 2 0 1 0 2.829-2.828l1.767 1.768a2 2 0 1 0 2.829-2.829l-6.364-6.364a2 2 0 1 0-2.829 2.829l1.768 1.767a2 2 0 0 0-2.828 2.829z"}],["path",{d:"m9.6 14.4 4.8-4.8"}]],N7=[["path",{d:"M6 18.5a3.5 3.5 0 1 0 7 0c0-1.57.92-2.52 2.04-3.46"}],["path",{d:"M6 8.5c0-.75.13-1.47.36-2.14"}],["path",{d:"M8.8 3.15A6.5 6.5 0 0 1 19 8.5c0 1.63-.44 2.81-1.09 3.76"}],["path",{d:"M12.5 6A2.5 2.5 0 0 1 15 8.5M10 13a2 2 0 0 0 1.82-1.18"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],K7=[["path",{d:"M6 8.5a6.5 6.5 0 1 1 13 0c0 6-6 6-6 10a3.5 3.5 0 1 1-7 0"}],["path",{d:"M15 8.5a2.5 2.5 0 0 0-5 0v1a2 2 0 1 1 0 4"}]],J7=[["path",{d:"M7 3.34V5a3 3 0 0 0 3 3"}],["path",{d:"M11 21.95V18a2 2 0 0 0-2-2 2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05"}],["path",{d:"M21.54 15H17a2 2 0 0 0-2 2v4.54"}],["path",{d:"M12 2a10 10 0 1 0 9.54 13"}],["path",{d:"M20 6V4a2 2 0 1 0-4 0v2"}],["rect",{width:"8",height:"5",x:"14",y:"6",rx:"1"}]],b1=[["path",{d:"M21.54 15H17a2 2 0 0 0-2 2v4.54"}],["path",{d:"M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17"}],["path",{d:"M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05"}],["circle",{cx:"12",cy:"12",r:"10"}]],Q7=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 2a7 7 0 1 0 10 10"}]],j7=[["circle",{cx:"11.5",cy:"12.5",r:"3.5"}],["path",{d:"M3 8c0-3.5 2.5-6 6.5-6 5 0 4.83 3 7.5 5s5 2 5 6c0 4.5-2.5 6.5-7 6.5-2.5 0-2.5 2.5-6 2.5s-7-2-7-5.5c0-3 1.5-3 1.5-5C3.5 10 3 9 3 8Z"}]],Y7=[["path",{d:"m2 2 20 20"}],["path",{d:"M20 14.347V14c0-6-4-12-8-12-1.078 0-2.157.436-3.157 1.19"}],["path",{d:"M6.206 6.21C4.871 8.4 4 11.2 4 14a8 8 0 0 0 14.568 4.568"}]],_7=[["path",{d:"M12 2C8 2 4 8 4 14a8 8 0 0 0 16 0c0-6-4-12-8-12"}]],T1=[["path",{d:"m19 6-5.2-3.9a.5.5 0 0 0-.8.4V18"}],["circle",{cx:"9",cy:"18",r:"4"}]],q1=[["path",{d:"M21 16V3a1 1 0 0 0-1.28-.96l-10 2.917a1 1 0 0 0-.72.96V19"}],["circle",{cx:"18",cy:"16",r:"3"}],["circle",{cx:"6",cy:"19",r:"3"}]],x7=[["path",{d:"M4 13a1 1 0 0 1-.72-1.695l7.257-7.668a2 2 0 0 1 2.926 0l7.256 7.668A1 1 0 0 1 20 13z"}],["rect",{x:"3",y:"17",width:"18",height:"4",rx:"1"}]],a9=[["ellipse",{cx:"12",cy:"12",rx:"10",ry:"6"}]],U1=[["circle",{cx:"12",cy:"12",r:"1"}],["circle",{cx:"12",cy:"5",r:"1"}],["circle",{cx:"12",cy:"19",r:"1"}]],O1=[["circle",{cx:"12",cy:"12",r:"1"}],["circle",{cx:"19",cy:"12",r:"1"}],["circle",{cx:"5",cy:"12",r:"1"}]],h9=[["path",{d:"M10 3h6"}],["path",{d:"M13 3v4"}],["path",{d:"M2 10v6"}],["path",{d:"M2 13h4"}],["path",{d:"M6 16a2 2 0 002 2h1a2 2 0 011.6.8l.3.4a2 2 0 001.6.8h2.264a2 2 0 001.789-1.106l1.67-3.341a1 1 0 01.895-.553H21a1 1 0 001-1v-4a1 1 0 00-1-1h-3.5a1 1 0 01-.8-.4l-.9-1.2A1 1 0 0015 7h-4a1 1 0 00-.8.4l-.9 1.2a1 1 0 01-.8.4H7a1 1 0 00-1 1z"}]],t9=[["path",{d:"M5 15a6.5 6.5 0 0 1 7 0 6.5 6.5 0 0 0 7 0"}],["path",{d:"M5 9a6.5 6.5 0 0 1 7 0 6.5 6.5 0 0 0 7 0"}],["line",{x1:"19",x2:"5",y1:"5",y2:"19"}]],d9=[["path",{d:"M5 15a6.5 6.5 0 0 1 7 0 6.5 6.5 0 0 0 7 0"}],["path",{d:"M5 9a6.5 6.5 0 0 1 7 0 6.5 6.5 0 0 0 7 0"}]],c9=[["line",{x1:"5",x2:"19",y1:"9",y2:"9"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15"}],["line",{x1:"19",x2:"5",y1:"5",y2:"19"}]],M9=[["line",{x1:"5",x2:"19",y1:"9",y2:"9"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15"}]],p9=[["path",{d:"M10 8v1"}],["path",{d:"M14 8v1"}],["path",{d:"M18 8v1"}],["path",{d:"M19 17a2 2 0 00-1.765 1.059l-.47.882A2 2 0 0115 20H9a2 2 0 01-1.765-1.059l-.47-.882A2 2 0 005 17H4a2 2 0 01-2-2V6a2 2 0 012-2h16a2 2 0 012 2v9a2 2 0 01-2 2z"}],["path",{d:"M6 8v1"}]],l9=[["path",{d:"M4 10h12"}],["path",{d:"M4 14h9"}],["path",{d:"M19 6a7.7 7.7 0 0 0-5.2-2A7.9 7.9 0 0 0 6 12c0 4.4 3.5 8 7.8 8 2 0 3.8-.8 5.2-2"}]],i9=[["path",{d:"M21 21H8a2 2 0 0 1-1.42-.587l-3.994-3.999a2 2 0 0 1 0-2.828l10-10a2 2 0 0 1 2.829 0l5.999 6a2 2 0 0 1 0 2.828L12.834 21"}],["path",{d:"m5.082 11.09 8.828 8.828"}]],n9=[["path",{d:"M14 13h2a2 2 0 0 1 2 2v2a2 2 0 0 0 4 0v-6.998a2 2 0 0 0-.59-1.42L18 5"}],["path",{d:"M14 21V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v16"}],["path",{d:"M2 21h13"}],["path",{d:"M3 7h11"}],["path",{d:"m9 11-2 3h3l-2 3"}]],e9=[["path",{d:"m15 15 6 6"}],["path",{d:"m15 9 6-6"}],["path",{d:"M21 16v5h-5"}],["path",{d:"M21 8V3h-5"}],["path",{d:"M3 16v5h5"}],["path",{d:"m3 21 6-6"}],["path",{d:"M3 8V3h5"}],["path",{d:"M9 9 3 3"}]],r9=[["path",{d:"M15 3h6v6"}],["path",{d:"M10 14 21 3"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"}]],o9=[["path",{d:"m15 18-.722-3.25"}],["path",{d:"M2 8a10.645 10.645 0 0 0 20 0"}],["path",{d:"m20 15-1.726-2.05"}],["path",{d:"m4 15 1.726-2.05"}],["path",{d:"m9 18 .722-3.25"}]],v9=[["path",{d:"M13.054 18.946a11 11 0 0 1-2.11 0"}],["path",{d:"M13.054 5.054a11 11 0 0 0-2.11-.001"}],["path",{d:"M17.072 6.274a11 11 0 0 1 1.753 1.173"}],["path",{d:"M18.825 16.552a11 11 0 0 1-1.753 1.174"}],["path",{d:"M2.514 13.303a11 11 0 0 1-.452-.954 1 1 0 0 1 0-.697 11 11 0 0 1 .45-.955"}],["path",{d:"M21.485 10.697a11 11 0 0 1 .453.955 1 1 0 0 1 0 .697 11 11 0 0 1-.453.954"}],["path",{d:"M5.173 7.448a11 11 0 0 1 1.753-1.174"}],["path",{d:"M6.926 17.726a11 11 0 0 1-1.753-1.174"}],["circle",{cx:"12",cy:"12",r:"3"}]],$9=[["path",{d:"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"}],["path",{d:"M14.084 14.158a3 3 0 0 1-4.242-4.242"}],["path",{d:"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"}],["path",{d:"m2 2 20 20"}]],m9=[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"}],["circle",{cx:"12",cy:"12",r:"3"}]],G1=[["path",{d:"M14 10h2"}],["path",{d:"M8 10h2"}],["path",{d:"M8 16h8"}],["circle",{cx:"12",cy:"12",r:"10"}]],Z1=[["path",{d:"M15 12v-1.584"}],["path",{d:"M17 10a5 5 0 00-3 1"}],["path",{d:"M7 10a5 5 0 013 1"}],["path",{d:"M9 12v-1.584"}],["path",{d:"M9 17a5 5 0 016.001 0"}],["circle",{cx:"12",cy:"12",r:"10"}]],E1=[["path",{d:"M15 10V9"}],["path",{d:"M7.084 14.302a5.12 5.12 0 009.833 0 .24.24 0 00-.235-.302H7.32a.24.24 0 00-.235.302"}],["path",{d:"M9 10V9"}],["circle",{cx:"12",cy:"12",r:"10"}]],W1=[["path",{d:"M15 10V9"}],["path",{d:"M8 16h8"}],["path",{d:"M9 10V9"}],["circle",{cx:"12",cy:"12",r:"10"}]],I1=[["path",{d:"M15 10V9"}],["path",{d:"M9 10V9"}],["path",{d:"M9 16a5 5 0 016 0"}],["circle",{cx:"12",cy:"12",r:"10"}]],X1=[["path",{d:"M13.267 2.08a10 10 0 108.653 8.653"}],["path",{d:"M15 10V9"}],["path",{d:"M16 5h6"}],["path",{d:"M16.472 15a6 6 0 01-8.943 0"}],["path",{d:"M19 2v6"}],["path",{d:"M9 10V9"}]],N1=[["path",{d:"M15 10V9"}],["path",{d:"M16.472 15a6 6 0 01-8.943 0"}],["path",{d:"M9 10V9"}],["circle",{cx:"12",cy:"12",r:"10"}]],y9=[["path",{d:"M12 16h.01"}],["path",{d:"M16 16h.01"}],["path",{d:"M3 19a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8.5a.5.5 0 0 0-.769-.422l-4.462 2.844A.5.5 0 0 1 15 10.5v-2a.5.5 0 0 0-.769-.422L9.77 10.922A.5.5 0 0 1 9 10.5V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2z"}],["path",{d:"M8 16h.01"}]],s9=[["path",{d:"M10.827 16.379a6.082 6.082 0 0 1-8.618-7.002l5.412 1.45a6.082 6.082 0 0 1 7.002-8.618l-1.45 5.412a6.082 6.082 0 0 1 8.618 7.002l-5.412-1.45a6.082 6.082 0 0 1-7.002 8.618l1.45-5.412Z"}],["path",{d:"M12 12v.01"}]],C9=[["path",{d:"M12 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 12 18z"}],["path",{d:"M2 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 2 18z"}]],u9=[["path",{d:"M10.083 5.428 5.57 4.083a2 2 0 10.001 3.834l4.512-1.345"}],["path",{d:"M12 8v3"}],["path",{d:"m13.917 5.428 4.511-1.345a2 2 0 110 3.834l-4.51-1.345"}],["path",{d:"M18 17v-4.006"}],["path",{d:"M22 11v8"}],["path",{d:"M22 12h-3a1 1 0 00-1 .994h-2.539a4 4 0 00-6.915-.012L7 13a5 5 0 00-5 5v1a1 1 0 001 1h2a1 1 0 001-1v-1a1 1 0 01.995-1l1.552.018a4 4 0 006.907 0L18 17a1 1 0 001 1h3"}],["circle",{cx:"12",cy:"6",r:"2"}]],g9=[["path",{d:"M4 3 2 5v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z"}],["path",{d:"M6 8h4"}],["path",{d:"M6 18h4"}],["path",{d:"m12 3-2 2v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z"}],["path",{d:"M14 8h4"}],["path",{d:"M14 18h4"}],["path",{d:"m20 3-2 2v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z"}]],A9=[["path",{d:"M14.086 18.412A2 2 0 0112.67 19H5v-7.672a2 2 0 01.586-1.414L11.75 3.75a6 6 0 118.49 8.49z"}],["path",{d:"M16 8 2 22"}],["path",{d:"M17.488 15H9"}]],H9=[["circle",{cx:"12",cy:"12",r:"2"}],["path",{d:"M12 2v4"}],["path",{d:"m6.8 15-3.5 2"}],["path",{d:"m20.7 7-3.5 2"}],["path",{d:"M6.8 9 3.3 7"}],["path",{d:"m20.7 17-3.5-2"}],["path",{d:"m9 22 3-8 3 8"}],["path",{d:"M8 22h8"}],["path",{d:"M18 18.7a9 9 0 1 0-12 0"}]],K1=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m8 18 4-4"}],["path",{d:"M8 10v8h8"}]],V9=[["path",{d:"M13.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v11.5"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 12v-1"}],["path",{d:"M8 18v-2"}],["path",{d:"M8 7V6"}],["circle",{cx:"8",cy:"20",r:"2"}]],J1=[["path",{d:"M13 22h5a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v3.3"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m7.69 16.479 1.29 4.88a.5.5 0 0 1-.698.591l-1.843-.849a1 1 0 0 0-.879.001l-1.846.85a.5.5 0 0 1-.692-.593l1.29-4.88"}],["circle",{cx:"6",cy:"14",r:"3"}]],Q1=[["path",{d:"M14 22h4a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M5 14a1 1 0 0 0-1 1v2a1 1 0 0 1-1 1 1 1 0 0 1 1 1v2a1 1 0 0 0 1 1"}],["path",{d:"M9 22a1 1 0 0 0 1-1v-2a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-2a1 1 0 0 0-1-1"}]],S9=[["path",{d:"M14 2v5a1 1 0 001 1h5"}],["path",{d:"M14.692 22H18a2 2 0 002-2V8a2.4 2.4 0 00-.706-1.706l-3.588-3.588A2.4 2.4 0 0014 2H6a2 2 0 00-2 2v3.804"}],["path",{d:"M2.264 13.752 7 16.5l4.737-2.748"}],["path",{d:"M2.995 13.014A2 2 0 002 14.744v3.516a2 2 0 00.996 1.73l3 1.74a2 2 0 002.008 0l3-1.74A2 2 0 0012 18.26v-3.517a2 2 0 00-.995-1.73l-3-1.742a2 2 0 00-1.892-.064z"}],["path",{d:"M7 16.5V22"}]],j1=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1"}],["path",{d:"M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1"}]],Y1=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 18v-2"}],["path",{d:"M12 18v-4"}],["path",{d:"M16 18v-6"}]],_1=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 18v-1"}],["path",{d:"M12 18v-6"}],["path",{d:"M16 18v-3"}]],x1=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m16 13-3.5 3.5-2-2L8 17"}]],a2=[["path",{d:"M10.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m14 20 2 2 4-4"}]],h2=[["path",{d:"M15.941 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.704l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v3.512"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M4.017 11.512a6 6 0 1 0 8.466 8.475"}],["path",{d:"M9 16a1 1 0 0 1-1-1v-4c0-.552.45-1.008.995-.917a6 6 0 0 1 4.922 4.922c.091.544-.365.995-.917.995z"}]],w9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m9 15 2 2 4-4"}]],L9=[["path",{d:"M16 22h2a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v2.85"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 14v2.2l1.6 1"}],["circle",{cx:"8",cy:"16",r:"6"}]],t2=[["path",{d:"M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m5 16-3 3 3 3"}],["path",{d:"m9 22 3-3-3-3"}]],f9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M10 12.5 8 15l2 2.5"}],["path",{d:"m14 12.5 2 2.5-2 2.5"}]],d2=[["path",{d:"M15 8a1 1 0 0 1-1-1V2a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8z"}],["path",{d:"M20 8v12a2 2 0 0 1-2 2h-4.182"}],["path",{d:"m3.305 19.53.923-.382"}],["path",{d:"M4 10.592V4a2 2 0 0 1 2-2h8"}],["path",{d:"m4.228 16.852-.924-.383"}],["path",{d:"m5.852 15.228-.383-.923"}],["path",{d:"m5.852 20.772-.383.924"}],["path",{d:"m8.148 15.228.383-.923"}],["path",{d:"m8.53 21.696-.382-.924"}],["path",{d:"m9.773 16.852.922-.383"}],["path",{d:"m9.773 19.148.922.383"}],["circle",{cx:"7",cy:"18",r:"3"}]],k9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M9 10h6"}],["path",{d:"M12 13V7"}],["path",{d:"M9 17h6"}]],P9=[["path",{d:"M4 12V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M10 16h2v6"}],["path",{d:"M10 22h4"}],["rect",{x:"2",y:"16",width:"4",height:"6",rx:"2"}]],B9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M12 18v-6"}],["path",{d:"m9 15 3 3 3-3"}]],c2=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M12 9v4"}],["path",{d:"M12 17h.01"}]],r=[["path",{d:"M4 6.835V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-.343"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M2 19a2 2 0 0 1 4 0v1a2 2 0 0 1-4 0v-4a6 6 0 0 1 12 0v4a2 2 0 0 1-4 0v-1a2 2 0 0 1 4 0"}]],z9=[["path",{d:"M13 22h5a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v7"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M3.62 18.8A2.25 2.25 0 1 1 7 15.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a1 1 0 0 1-1.507 0z"}]],D9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["circle",{cx:"10",cy:"12",r:"2"}],["path",{d:"m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22"}]],F9=[["path",{d:"M4 11V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-1"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M2 15h10"}],["path",{d:"m9 18 3-3-3-3"}]],M2=[["path",{d:"M4 9.8V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M9 17v-2a2 2 0 0 0-4 0v2"}],["rect",{width:"8",height:"5",x:"3",y:"17",rx:"1"}]],p2=[["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M4 12v6"}],["path",{d:"M4 14h2"}],["path",{d:"M9.65 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v4"}],["circle",{cx:"4",cy:"20",r:"2"}]],l2=[["path",{d:"M20 14V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M14 18h6"}]],R9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M9 15h6"}]],b9=[["path",{d:"M11.65 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v10.35"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 20v-7l3 1.474"}],["circle",{cx:"6",cy:"20",r:"2"}]],T9=[["path",{d:"M4.226 20.925A2 2 0 0 0 6 22h12a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v3.127"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m5 11-3 3"}],["path",{d:"m5 17-3-3h10"}]],i2=[["path",{d:"M14.364 13.634a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506l4.013-4.009a1 1 0 0 0-3.004-3.004z"}],["path",{d:"M14.487 7.858A1 1 0 0 1 14 7V2"}],["path",{d:"M20 19.645V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l2.516 2.516"}],["path",{d:"M8 18h1"}]],n2=[["path",{d:"M12.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v9.34"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M10.378 12.622a1 1 0 0 1 3 3.003L8.36 20.637a2 2 0 0 1-.854.506l-2.867.837a.5.5 0 0 1-.62-.62l.836-2.869a2 2 0 0 1 .506-.853z"}]],e2=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M15.033 13.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56v-4.704a.645.645 0 0 1 .967-.56z"}]],r2=[["path",{d:"M11.35 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v5.35"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M14 19h6"}],["path",{d:"M17 16v6"}]],q9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M9 15h6"}],["path",{d:"M12 18v-6"}]],o2=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M12 17h.01"}],["path",{d:"M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3"}]],U9=[["path",{d:"M20 10V8a2.4 2.4 0 0 0-.706-1.704l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4.35"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M16 14a2 2 0 0 0-2 2"}],["path",{d:"M16 22a2 2 0 0 1-2-2"}],["path",{d:"M20 14a2 2 0 0 1 2 2"}],["path",{d:"M20 22a2 2 0 0 0 2-2"}]],v2=[["path",{d:"M11.1 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.589 3.588A2.4 2.4 0 0 1 20 8v3.25"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m21 22-2.88-2.88"}],["circle",{cx:"16",cy:"17",r:"3"}]],O9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["circle",{cx:"11.5",cy:"14.5",r:"2.5"}],["path",{d:"M13.3 16.3 15 18"}]],$2=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 15h.01"}],["path",{d:"M11.5 13.5a2.5 2.5 0 0 1 0 3"}],["path",{d:"M15 12a5 5 0 0 1 0 6"}]],G9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 12h8"}],["path",{d:"M10 11v2"}],["path",{d:"M8 17h8"}],["path",{d:"M14 16v2"}]],Z9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 13h2"}],["path",{d:"M14 13h2"}],["path",{d:"M8 17h2"}],["path",{d:"M14 17h2"}]],E9=[["path",{d:"M11 21a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1"}],["path",{d:"M16 16a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1"}],["path",{d:"M21 6a2 2 0 0 0-.586-1.414l-2-2A2 2 0 0 0 17 2h-3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1z"}]],W9=[["path",{d:"M4 11V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h7"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m10 18 3-3-3-3"}]],I9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m8 16 2-2-2-2"}],["path",{d:"M12 18h4"}]],X9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M10 9H8"}],["path",{d:"M16 13H8"}],["path",{d:"M16 17H8"}]],m2=[["path",{d:"M12 22h6a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M3 16v-1.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5V16"}],["path",{d:"M6 22h2"}],["path",{d:"M7 14v8"}]],N9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M11 18h2"}],["path",{d:"M12 12v6"}],["path",{d:"M9 13v-.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v.5"}]],K9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M12 12v6"}],["path",{d:"m15 15-3-3-3 3"}]],J9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M16 22a4 4 0 0 0-8 0"}],["circle",{cx:"12",cy:"15",r:"3"}]],y2=[["path",{d:"M4 12V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m10 17.843 3.033-1.755a.64.64 0 0 1 .967.56v4.704a.65.65 0 0 1-.967.56L10 20.157"}],["rect",{width:"7",height:"6",x:"3",y:"16",rx:"1"}]],Q9=[["path",{d:"M4 11.55V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-1.95"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M12 15a5 5 0 0 1 0 6"}],["path",{d:"M8 14.502a.5.5 0 0 0-.826-.381l-1.893 1.631a1 1 0 0 1-.651.243H3.5a.5.5 0 0 0-.5.501v3.006a.5.5 0 0 0 .5.501h1.129a1 1 0 0 1 .652.243l1.893 1.633a.5.5 0 0 0 .826-.38z"}]],s2=[["path",{d:"M11 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v5"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m15 17 5 5"}],["path",{d:"m20 17-5 5"}]],j9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"m14.5 12.5-5 5"}],["path",{d:"m9.5 12.5 5 5"}]],Y9=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}]],_9=[["path",{d:"M15 2h-4a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V8"}],["path",{d:"M16.706 2.706A2.4 2.4 0 0 0 15 2v5a1 1 0 0 0 1 1h5a2.4 2.4 0 0 0-.706-1.706z"}],["path",{d:"M5 7a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h8a2 2 0 0 0 1.732-1"}]],x9=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M7 3v18"}],["path",{d:"M3 7.5h4"}],["path",{d:"M3 12h18"}],["path",{d:"M3 16.5h4"}],["path",{d:"M17 3v18"}],["path",{d:"M17 7.5h4"}],["path",{d:"M17 16.5h4"}]],C2=[["path",{d:"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4"}],["path",{d:"M14 13.12c0 2.38 0 6.38-1 8.88"}],["path",{d:"M17.29 21.02c.12-.6.43-2.3.5-3.02"}],["path",{d:"M2 12a10 10 0 0 1 18-6"}],["path",{d:"M2 16h.01"}],["path",{d:"M21.8 16c.2-2 .131-5.354 0-6"}],["path",{d:"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2"}],["path",{d:"M8.65 22c.21-.66.45-1.32.57-2"}],["path",{d:"M9 6.8a6 6 0 0 1 9 5.2v2"}]],aM=[["path",{d:"M15 6.5V3a1 1 0 0 0-1-1h-2a1 1 0 0 0-1 1v3.5"}],["path",{d:"M9 18h8"}],["path",{d:"M18 3h-3"}],["path",{d:"M11 3a6 6 0 0 0-6 6v11"}],["path",{d:"M5 13h4"}],["path",{d:"M17 10a4 4 0 0 0-8 0v10a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2Z"}]],hM=[["path",{d:"M18 12.47v.03m0-.5v.47m-.475 5.056A6.744 6.744 0 0 1 15 18c-3.56 0-7.56-2.53-8.5-6 .348-1.28 1.114-2.433 2.121-3.38m3.444-2.088A8.802 8.802 0 0 1 15 6c3.56 0 6.06 2.54 7 6-.309 1.14-.786 2.177-1.413 3.058"}],["path",{d:"M7 10.67C7 8 5.58 5.97 2.73 5.5c-1 1.5-1 5 .23 6.5-1.24 1.5-1.24 5-.23 6.5C5.58 18.03 7 16 7 13.33m7.48-4.372A9.77 9.77 0 0 1 16 6.07m0 11.86a9.77 9.77 0 0 1-1.728-3.618"}],["path",{d:"m16.01 17.93-.23 1.4A2 2 0 0 1 13.8 21H9.5a5.96 5.96 0 0 0 1.49-3.98M8.53 3h5.27a2 2 0 0 1 1.98 1.67l.23 1.4M2 2l20 20"}]],tM=[["path",{d:"M2 16s9-15 20-4C11 23 2 8 2 8"}]],dM=[["path",{d:"M6.5 12c.94-3.46 4.94-6 8.5-6 3.56 0 6.06 2.54 7 6-.94 3.47-3.44 6-7 6s-7.56-2.53-8.5-6Z"}],["path",{d:"M18 12v.5"}],["path",{d:"M16 17.93a9.77 9.77 0 0 1 0-11.86"}],["path",{d:"M7 10.67C7 8 5.58 5.97 2.73 5.5c-1 1.5-1 5 .23 6.5-1.24 1.5-1.24 5-.23 6.5C5.58 18.03 7 16 7 13.33"}],["path",{d:"M10.46 7.26C10.2 5.88 9.17 4.24 8 3h5.8a2 2 0 0 1 1.98 1.67l.23 1.4"}],["path",{d:"m16.01 17.93-.23 1.4A2 2 0 0 1 13.8 21H9.5a5.96 5.96 0 0 0 1.49-3.98"}]],cM=[["path",{d:"m17.586 11.414-5.93 5.93a1 1 0 0 1-8-8l3.137-3.137a.707.707 0 0 1 1.207.5V10"}],["path",{d:"M20.414 8.586 22 7"}],["circle",{cx:"19",cy:"10",r:"2"}]],MM=[["path",{d:"M4 11h1"}],["path",{d:"M8 15a2 2 0 0 1-4 0V3a1 1 0 0 1 1-1h.5C14 2 20 9 20 18v4"}],["circle",{cx:"18",cy:"18",r:"2"}]],pM=[["path",{d:"M16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528"}],["path",{d:"m2 2 20 20"}],["path",{d:"M4 22V4"}],["path",{d:"M7.656 2H8c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10.347"}]],lM=[["path",{d:"M18 22V2.8a.8.8 0 0 0-1.17-.71L5.45 7.78a.8.8 0 0 0 0 1.44L18 15.5"}]],iM=[["path",{d:"M6 22V2.8a.8.8 0 0 1 1.17-.71l11.38 5.69a.8.8 0 0 1 0 1.44L6 15.5"}]],nM=[["path",{d:"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528"}]],eM=[["path",{d:"M12 2c1 3 2.5 3.5 3.5 4.5A5 5 0 0 1 17 10a5 5 0 1 1-10 0c0-.3 0-.6.1-.9a2 2 0 1 0 3.3-2C8 4.5 11 2 12 2Z"}],["path",{d:"m5 22 14-4"}],["path",{d:"m5 18 14 4"}]],rM=[["path",{d:"M12 3q1 4 4 6.5t3 5.5a1 1 0 0 1-14 0 5 5 0 0 1 1-3 1 1 0 0 0 5 0c0-2-1.5-3-1.5-5q0-2 2.5-4"}]],oM=[["path",{d:"M11.652 6H18"}],["path",{d:"M12 13v1"}],["path",{d:"M16 16v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-8a4 4 0 0 0-.8-2.4l-.6-.8A3 3 0 0 1 6 7V6"}],["path",{d:"m2 2 20 20"}],["path",{d:"M7.649 2H17a1 1 0 0 1 1 1v4a3 3 0 0 1-.6 1.8l-.6.8a4 4 0 0 0-.55 1.007"}]],vM=[["path",{d:"M12 13v1"}],["path",{d:"M17 2a1 1 0 0 1 1 1v4a3 3 0 0 1-.6 1.8l-.6.8A4 4 0 0 0 16 12v8a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-8a4 4 0 0 0-.8-2.4l-.6-.8A3 3 0 0 1 6 7V3a1 1 0 0 1 1-1z"}],["path",{d:"M6 6h12"}]],$M=[["path",{d:"M10 2v2.343"}],["path",{d:"M14 2v6.343"}],["path",{d:"m2 2 20 20"}],["path",{d:"M20 20a2 2 0 0 1-2 2H6a2 2 0 0 1-1.755-2.96l5.227-9.563"}],["path",{d:"M6.453 15H15"}],["path",{d:"M8.5 2h7"}]],mM=[["path",{d:"M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"}],["path",{d:"M6.453 15h11.094"}],["path",{d:"M8.5 2h7"}]],yM=[["path",{d:"M10 2v6.292a7 7 0 1 0 4 0V2"}],["path",{d:"M5 15h14"}],["path",{d:"M8.5 2h7"}]],sM=[["path",{d:"M12 5a3 3 0 1 1 3 3m-3-3a3 3 0 1 0-3 3m3-3v1M9 8a3 3 0 1 0 3 3M9 8h1m5 0a3 3 0 1 1-3 3m3-3h-1m-2 3v-1"}],["circle",{cx:"12",cy:"8",r:"2"}],["path",{d:"M12 10v12"}],["path",{d:"M12 22c4.2 0 7-1.667 7-5-4.2 0-7 1.667-7 5Z"}],["path",{d:"M12 22c-4.2 0-7-1.667-7-5 4.2 0 7 1.667 7 5Z"}]],CM=[["circle",{cx:"12",cy:"12",r:"3"}],["path",{d:"M12 16.5A4.5 4.5 0 1 1 7.5 12 4.5 4.5 0 1 1 12 7.5a4.5 4.5 0 1 1 4.5 4.5 4.5 4.5 0 1 1-4.5 4.5"}],["path",{d:"M12 7.5V9"}],["path",{d:"M7.5 12H9"}],["path",{d:"M16.5 12H15"}],["path",{d:"M12 16.5V15"}],["path",{d:"m8 8 1.88 1.88"}],["path",{d:"M14.12 9.88 16 8"}],["path",{d:"m8 16 1.88-1.88"}],["path",{d:"M14.12 14.12 16 16"}]],uM=[["circle",{cx:"12",cy:"12",r:"3"}],["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}]],gM=[["path",{d:"M2 12h6"}],["path",{d:"M22 12h-6"}],["path",{d:"M12 2v2"}],["path",{d:"M12 8v2"}],["path",{d:"M12 14v2"}],["path",{d:"M12 20v2"}],["path",{d:"m19 9-3 3 3 3"}],["path",{d:"m5 15 3-3-3-3"}]],AM=[["path",{d:"M12 22v-6"}],["path",{d:"M12 8V2"}],["path",{d:"M4 12H2"}],["path",{d:"M10 12H8"}],["path",{d:"M16 12h-2"}],["path",{d:"M22 12h-2"}],["path",{d:"m15 19-3-3-3 3"}],["path",{d:"m15 5-3 3-3-3"}]],HM=[["circle",{cx:"15",cy:"19",r:"2"}],["path",{d:"M20.9 19.8A2 2 0 0 0 22 18V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h5.1"}],["path",{d:"M15 11v-1"}],["path",{d:"M15 17v-2"}]],VM=[["path",{d:"M12 6v7.751a.25.25 0 00.407.195l2.28-1.834a.5.5 0 01.627 0l2.28 1.834a.25.25 0 00.406-.195V6"}],["path",{d:"M20 20a2 2 0 002-2V8a2 2 0 00-2-2h-7.9a2 2 0 01-1.69-.9L9.6 3.9A2 2 0 007.93 3H4a2 2 0 00-2 2v13a2 2 0 002 2z"}]],SM=[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}],["path",{d:"m9 13 2 2 4-4"}]],wM=[["path",{d:"M16 14v2.2l1.6 1"}],["path",{d:"M7 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2"}],["circle",{cx:"16",cy:"16",r:"6"}]],LM=[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}],["path",{d:"M2 10h20"}]],fM=[["path",{d:"M10 10.5 8 13l2 2.5"}],["path",{d:"m14 10.5 2 2.5-2 2.5"}],["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z"}]],u2=[["path",{d:"M10.3 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.98a2 2 0 0 1 1.69.9l.66 1.2A2 2 0 0 0 12 6h8a2 2 0 0 1 2 2v3.3"}],["path",{d:"m14.305 19.53.923-.382"}],["path",{d:"m15.228 16.852-.923-.383"}],["path",{d:"m16.852 15.228-.383-.923"}],["path",{d:"m16.852 20.772-.383.924"}],["path",{d:"m19.148 15.228.383-.923"}],["path",{d:"m19.53 21.696-.382-.924"}],["path",{d:"m20.772 16.852.924-.383"}],["path",{d:"m20.772 19.148.924.383"}],["circle",{cx:"18",cy:"18",r:"3"}]],kM=[["path",{d:"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"}],["circle",{cx:"12",cy:"13",r:"1"}]],PM=[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}],["path",{d:"M12 10v6"}],["path",{d:"m15 13-3 3-3-3"}]],BM=[["path",{d:"M18 19a5 5 0 0 1-5-5v8"}],["path",{d:"M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5"}],["circle",{cx:"13",cy:"12",r:"2"}],["circle",{cx:"20",cy:"19",r:"2"}]],zM=[["circle",{cx:"12",cy:"13",r:"2"}],["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}],["path",{d:"M14 13h3"}],["path",{d:"M7 13h3"}]],DM=[["path",{d:"M10.638 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v3.417"}],["path",{d:"M14.62 18.8A2.25 2.25 0 1 1 18 15.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a.998.998 0 0 1-1.507 0z"}]],FM=[["path",{d:"M2 9V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-1"}],["path",{d:"M2 13h10"}],["path",{d:"m9 16 3-3-3-3"}]],RM=[["path",{d:"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"}],["path",{d:"M8 10v4"}],["path",{d:"M12 10v2"}],["path",{d:"M16 10v6"}]],bM=[["path",{d:"M13 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v1.36"}],["path",{d:"M19 12v6"}],["path",{d:"M19 14h2"}],["circle",{cx:"19",cy:"20",r:"2"}]],TM=[["rect",{width:"8",height:"5",x:"14",y:"17",rx:"1"}],["path",{d:"M10 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v2.5"}],["path",{d:"M20 17v-2a2 2 0 1 0-4 0v2"}]],qM=[["path",{d:"M9 13h6"}],["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}]],UM=[["path",{d:"m6 14 1.45-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6a2 2 0 0 1-1.94 1.5H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H18a2 2 0 0 1 2 2v2"}],["circle",{cx:"14",cy:"15",r:"1"}]],OM=[["path",{d:"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"}]],GM=[["path",{d:"M2 7.5V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-1.5"}],["path",{d:"M2 13h10"}],["path",{d:"m5 10-3 3 3 3"}]],g2=[["path",{d:"M2 11.5V5a2 2 0 0 1 2-2h3.9c.7 0 1.3.3 1.7.9l.8 1.2c.4.6 1 .9 1.7.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-9.5"}],["path",{d:"M11.378 13.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"}]],ZM=[["path",{d:"M12 10v6"}],["path",{d:"M9 13h6"}],["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}]],EM=[["path",{d:"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"}],["circle",{cx:"12",cy:"13",r:"2"}],["path",{d:"M12 15v5"}]],WM=[["circle",{cx:"11.5",cy:"12.5",r:"2.5"}],["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}],["path",{d:"M13.3 14.3 15 16"}]],IM=[["path",{d:"M10.7 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4.1"}],["path",{d:"m21 21-1.9-1.9"}],["circle",{cx:"17",cy:"17",r:"3"}]],XM=[["path",{d:"M2 9.35V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h7"}],["path",{d:"m8 16 3-3-3-3"}]],NM=[["path",{d:"M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v.5"}],["path",{d:"M12 10v4h4"}],["path",{d:"m12 14 1.535-1.605a5 5 0 0 1 8 1.5"}],["path",{d:"M22 22v-4h-4"}],["path",{d:"m22 18-1.535 1.605a5 5 0 0 1-8-1.5"}]],KM=[["path",{d:"M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"}],["path",{d:"M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"}],["path",{d:"M3 5a2 2 0 0 0 2 2h3"}],["path",{d:"M3 3v13a2 2 0 0 0 2 2h3"}]],JM=[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}],["path",{d:"M12 10v6"}],["path",{d:"m9 13 3-3 3 3"}]],QM=[["path",{d:"M10.254 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2V9.255"}],["path",{d:"M22 22a4 4 0 0 0-8 0"}],["circle",{cx:"18",cy:"15",r:"3"}]],jM=[["path",{d:"M5.254 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2V9.255"}],["circle",{cx:"13",cy:"15",r:"3"}],["path",{d:"M17 22a4 4 0 0 0-8 0"}],["path",{d:"M22 22A4 4 0 0 0 20.24 18.686 1 1 0 0 1 20.1 17.143 3 3 0 0 0 19.5 12.402"}]],YM=[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}],["path",{d:"m9.5 10.5 5 5"}],["path",{d:"m14.5 10.5-5 5"}]],_M=[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}]],xM=[["path",{d:"M20 5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h2.5a1.5 1.5 0 0 1 1.2.6l.6.8a1.5 1.5 0 0 0 1.2.6z"}],["path",{d:"M3 8.268a2 2 0 0 0-1 1.738V19a2 2 0 0 0 2 2h11a2 2 0 0 0 1.732-1"}]],ap=[["path",{d:"M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z"}],["path",{d:"M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z"}],["path",{d:"M16 17h4"}],["path",{d:"M4 13h4"}]],hp=[["path",{d:"M12 12H5a2 2 0 0 0-2 2v5"}],["path",{d:"M15 19h7"}],["path",{d:"M16 19V2"}],["path",{d:"M6 12V7a2 2 0 0 1 2-2h2.172a2 2 0 0 1 1.414.586l3.828 3.828A2 2 0 0 1 16 10.828"}],["path",{d:"M7 19h4"}],["circle",{cx:"13",cy:"19",r:"2"}],["circle",{cx:"5",cy:"19",r:"2"}]],tp=[["path",{d:"M4 14h6"}],["path",{d:"M4 2h10"}],["rect",{x:"4",y:"18",width:"16",height:"4",rx:"1"}],["rect",{x:"4",y:"6",width:"16",height:"4",rx:"1"}]],dp=[["path",{d:"m15 17 5-5-5-5"}],["path",{d:"M4 18v-2a4 4 0 0 1 4-4h12"}]],cp=[["line",{x1:"22",x2:"2",y1:"6",y2:"6"}],["line",{x1:"22",x2:"2",y1:"18",y2:"18"}],["line",{x1:"6",x2:"6",y1:"2",y2:"22"}],["line",{x1:"18",x2:"18",y1:"2",y2:"22"}]],Mp=[["path",{d:"M14 13h2a2 2 0 0 1 2 2v2a2 2 0 0 0 4 0v-6.998a2 2 0 0 0-.59-1.42L18 5"}],["path",{d:"M14 21V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v16"}],["path",{d:"M2 21h13"}],["path",{d:"M3 9h11"}]],pp=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["rect",{width:"10",height:"8",x:"7",y:"8",rx:"1"}]],lp=[["path",{d:"M13.354 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14v6a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341l1.218-1.348"}],["path",{d:"M16 6h6"}],["path",{d:"M19 3v6"}]],A2=[["path",{d:"M12.531 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14v6a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341l.427-.473"}],["path",{d:"m16.5 3.5 5 5"}],["path",{d:"m21.5 3.5-5 5"}]],H2=[["path",{d:"M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"}]],ip=[["path",{d:"M16.005 15.108a5.041 6.52 28.25 00-8.008-6.217 5.041 6.52 28.25 008.008 6.217A11.884 7.288-60.76 014.029 7.001"}],["path",{d:"M17 21h.01"}],["path",{d:"M7 3h.01"}],["path",{d:"M7.997 8.891a11.885 7.288-60.756 0111.977 8.107"}],["circle",{cx:"12",cy:"12",r:"1",fill:"currentColor"}]],np=[["path",{d:"M2 3v18"}],["rect",{width:"12",height:"18",x:"6",y:"3",rx:"2"}],["path",{d:"M22 3v18"}]],ep=[["path",{d:"M2 7v10"}],["path",{d:"M6 5v14"}],["rect",{width:"12",height:"18",x:"10",y:"3",rx:"2"}]],rp=[["rect",{width:"18",height:"14",x:"3",y:"3",rx:"2"}],["path",{d:"M4 21h1"}],["path",{d:"M9 21h1"}],["path",{d:"M14 21h1"}],["path",{d:"M19 21h1"}]],op=[["path",{d:"M7 2h10"}],["path",{d:"M5 6h14"}],["rect",{width:"18",height:"12",x:"3",y:"10",rx:"2"}]],vp=[["path",{d:"M3 2h18"}],["rect",{width:"18",height:"12",x:"3",y:"6",rx:"2"}],["path",{d:"M3 22h18"}]],$p=[["line",{x1:"6",x2:"10",y1:"11",y2:"11"}],["line",{x1:"8",x2:"8",y1:"9",y2:"13"}],["line",{x1:"15",x2:"15.01",y1:"12",y2:"12"}],["line",{x1:"18",x2:"18.01",y1:"10",y2:"10"}],["path",{d:"M17.32 5H6.68a4 4 0 0 0-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 0 0 3 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 0 1 9.828 16h4.344a2 2 0 0 1 1.414.586L17 18c.5.5 1 1 2 1a3 3 0 0 0 3-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0 0 17.32 5z"}]],mp=[["path",{d:"M11.146 15.854a1.207 1.207 0 0 1 1.708 0l1.56 1.56A2 2 0 0 1 15 18.828V21a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1v-2.172a2 2 0 0 1 .586-1.414z"}],["path",{d:"M18.828 15a2 2 0 0 1-1.414-.586l-1.56-1.56a1.207 1.207 0 0 1 0-1.708l1.56-1.56A2 2 0 0 1 18.828 9H21a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1z"}],["path",{d:"M6.586 14.414A2 2 0 0 1 5.172 15H3a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2.172a2 2 0 0 1 1.414.586l1.56 1.56a1.207 1.207 0 0 1 0 1.708z"}],["path",{d:"M9 3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2.172a2 2 0 0 1-.586 1.414l-1.56 1.56a1.207 1.207 0 0 1-1.708 0l-1.56-1.56A2 2 0 0 1 9 5.172z"}]],yp=[["line",{x1:"6",x2:"10",y1:"12",y2:"12"}],["line",{x1:"8",x2:"8",y1:"10",y2:"14"}],["line",{x1:"15",x2:"15.01",y1:"13",y2:"13"}],["line",{x1:"18",x2:"18.01",y1:"11",y2:"11"}],["rect",{width:"20",height:"12",x:"2",y:"6",rx:"2"}]],sp=[["path",{d:"M12 2v2"}],["path",{d:"M12 8v2"}],["path",{d:"M12 14v2"}],["path",{d:"M12 20v2"}],["path",{d:"M21 3h-3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3"}],["path",{d:"M3 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H3"}]],Cp=[["path",{d:"M2 12h2"}],["path",{d:"M8 12h2"}],["path",{d:"M14 12h2"}],["path",{d:"M20 12h2"}],["path",{d:"M3 21v-3a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v3"}],["path",{d:"M3 3v3a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V3"}]],up=[["path",{d:"m12 14 4-4"}],["path",{d:"M3.34 19a10 10 0 1 1 17.32 0"}]],gp=[["path",{d:"m14 13-8.381 8.38a1 1 0 0 1-3.001-3l8.384-8.381"}],["path",{d:"m16 16 6-6"}],["path",{d:"m21.5 10.5-8-8"}],["path",{d:"m8 8 6-6"}],["path",{d:"m8.5 7.5 8 8"}]],Ap=[["path",{d:"M10.5 3 8 9l4 13 4-13-2.5-6"}],["path",{d:"M17 3a2 2 0 0 1 1.6.8l3 4a2 2 0 0 1 .013 2.382l-7.99 10.986a2 2 0 0 1-3.247 0l-7.99-10.986A2 2 0 0 1 2.4 7.8l2.998-3.997A2 2 0 0 1 7 3z"}],["path",{d:"M2 9h20"}]],Hp=[["path",{d:"m11 2 .925 1.848"}],["path",{d:"M13 15h.01"}],["path",{d:"M13.424 7.768a2 2 0 112.808 2.808"}],["path",{d:"m16 21-1-2.472"}],["path",{d:"M16.988 16.988A12 12 0 019 20a5 5 0 01-2.759-9.171 8.8 8.8 0 002.307-2.28"}],["path",{d:"m19 2-1 1.804"}],["path",{d:"m2 19 2.746-1.373"}],["path",{d:"m2 2 20 20"}],["path",{d:"m22 16-2.474-2.13a12 12 0 001.376-3.786 6 6 0 00-10.313-5.151"}],["path",{d:"m22 5-1.804 1"}],["path",{d:"m3 10 2 2"}],["path",{d:"M9 16h.01"}],["path",{d:"M9 20v2"}]],Vp=[["path",{d:"M11.5 21a7.5 7.5 0 1 1 7.35-9"}],["path",{d:"M13 12V3"}],["path",{d:"M4 21h16"}],["path",{d:"M9 12V3"}]],Sp=[["path",{d:"m11 2 .925 1.848"}],["path",{d:"M13 15h.01"}],["path",{d:"m16 21-1-2.472"}],["path",{d:"m19 2-1 1.804"}],["path",{d:"m2 19 2.746-1.373"}],["path",{d:"m22 16-2.474-2.13"}],["path",{d:"m22 5-1.804 1"}],["path",{d:"m3 10 2 2"}],["path",{d:"M9 16h.01"}],["path",{d:"M9 20v2"}],["path",{d:"M9.33 7.035c-.51 1.478-1.786 2.93-3.09 3.794A5 5 0 009 20a12.1 12.1 0 0011.902-9.916A6 6 0 009.33 7.035"}],["circle",{cx:"15",cy:"9",r:"2"}]],wp=[["path",{d:"M15 10v1"}],["path",{d:"M7.528 20.472a1.6 1.6 0 012.277 0l1.057 1.056a1.6 1.6 0 002.276 0l1.057-1.056a1.6 1.6 0 012.277 0l1.114 1.114a1.4 1.4 0 002.414-1V10a8 8 0 00-16 0v10.586a1.4 1.4 0 002.414 1z"}],["path",{d:"M9 10v1"}]],Lp=[["path",{d:"M12 7v14"}],["path",{d:"M20 11v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8"}],["path",{d:"M7.5 7a1 1 0 0 1 0-5A4.8 8 0 0 1 12 7a4.8 8 0 0 1 4.5-5 1 1 0 0 1 0 5"}],["rect",{x:"3",y:"7",width:"18",height:"4",rx:"1"}]],fp=[["path",{d:"M15 6a9 9 0 0 0-9 9V3"}],["path",{d:"M21 18h-6"}],["circle",{cx:"18",cy:"6",r:"3"}],["circle",{cx:"6",cy:"18",r:"3"}]],kp=[["path",{d:"M6 3v12"}],["path",{d:"M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"}],["path",{d:"M6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"}],["path",{d:"M15 6a9 9 0 0 0-9 9"}],["path",{d:"M18 15v6"}],["path",{d:"M21 18h-6"}]],Pp=[["path",{d:"M15 6a9 9 0 0 0-9 9V3"}],["circle",{cx:"18",cy:"6",r:"3"}],["circle",{cx:"6",cy:"18",r:"3"}]],V2=[["circle",{cx:"12",cy:"12",r:"3"}],["line",{x1:"3",x2:"9",y1:"12",y2:"12"}],["line",{x1:"15",x2:"21",y1:"12",y2:"12"}]],Bp=[["path",{d:"M12 3v6"}],["circle",{cx:"12",cy:"12",r:"3"}],["path",{d:"M12 15v6"}]],zp=[["circle",{cx:"5",cy:"6",r:"3"}],["path",{d:"M12 6h5a2 2 0 0 1 2 2v7"}],["path",{d:"m15 9-3-3 3-3"}],["circle",{cx:"19",cy:"18",r:"3"}],["path",{d:"M12 18H7a2 2 0 0 1-2-2V9"}],["path",{d:"m9 15 3 3-3 3"}]],Dp=[["circle",{cx:"18",cy:"18",r:"3"}],["circle",{cx:"6",cy:"6",r:"3"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7"}],["path",{d:"M11 18H8a2 2 0 0 1-2-2V9"}]],Fp=[["circle",{cx:"12",cy:"18",r:"3"}],["circle",{cx:"6",cy:"6",r:"3"}],["circle",{cx:"18",cy:"6",r:"3"}],["path",{d:"M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9"}],["path",{d:"M12 12v3"}]],Rp=[["circle",{cx:"5",cy:"6",r:"3"}],["path",{d:"M5 9v6"}],["circle",{cx:"5",cy:"18",r:"3"}],["path",{d:"M12 3v18"}],["circle",{cx:"19",cy:"6",r:"3"}],["path",{d:"M16 15.7A9 9 0 0 0 19 9"}]],bp=[["path",{d:"M12 6h4a2 2 0 0 1 2 2v7"}],["path",{d:"M6 12v9"}],["path",{d:"m8.5 3.5-5 5"}],["path",{d:"m8.5 8.5-5-5"}],["circle",{cx:"18",cy:"18",r:"3"}]],Tp=[["circle",{cx:"18",cy:"18",r:"3"}],["circle",{cx:"6",cy:"6",r:"3"}],["path",{d:"M6 21V9a9 9 0 0 0 9 9"}]],qp=[["circle",{cx:"5",cy:"6",r:"3"}],["path",{d:"M5 9v12"}],["circle",{cx:"19",cy:"18",r:"3"}],["path",{d:"m15 9-3-3 3-3"}],["path",{d:"M12 6h5a2 2 0 0 1 2 2v7"}]],Up=[["circle",{cx:"5",cy:"6",r:"3"}],["path",{d:"M5 9v12"}],["path",{d:"m15 9-3-3 3-3"}],["path",{d:"M12 6h5a2 2 0 0 1 2 2v3"}],["path",{d:"M19 15v6"}],["path",{d:"M22 18h-6"}]],Op=[["circle",{cx:"6",cy:"6",r:"3"}],["path",{d:"M6 9v12"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v3"}],["path",{d:"M18 15v6"}],["path",{d:"M21 18h-6"}]],Gp=[["path",{d:"m15.5 3.5 5 5"}],["path",{d:"m15.5 8.5 5-5"}],["path",{d:"M18 11.62V15"}],["path",{d:"M6 9v12"}],["circle",{cx:"18",cy:"18",r:"3"}],["circle",{cx:"6",cy:"6",r:"3"}]],Zp=[["circle",{cx:"18",cy:"18",r:"3"}],["circle",{cx:"6",cy:"6",r:"3"}],["path",{d:"M18 6V5"}],["path",{d:"M18 11v-1"}],["line",{x1:"6",x2:"6",y1:"9",y2:"21"}]],Ep=[["circle",{cx:"18",cy:"18",r:"3"}],["circle",{cx:"6",cy:"6",r:"3"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7"}],["line",{x1:"6",x2:"6",y1:"9",y2:"21"}]],Wp=[["path",{d:"M5.116 4.104A1 1 0 0 1 6.11 3h11.78a1 1 0 0 1 .994 1.105L17.19 20.21A2 2 0 0 1 15.2 22H8.8a2 2 0 0 1-2-1.79z"}],["path",{d:"M6 12a5 5 0 0 1 6 0 5 5 0 0 0 6 0"}]],Ip=[["circle",{cx:"6",cy:"15",r:"4"}],["circle",{cx:"18",cy:"15",r:"4"}],["path",{d:"M14 15a2 2 0 0 0-2-2 2 2 0 0 0-2 2"}],["path",{d:"M2.5 13 5 7c.7-1.3 1.4-2 3-2"}],["path",{d:"M21.5 13 19 7c-.7-1.3-1.5-2-3-2"}]],Xp=[["path",{d:"m15 6 2 2 4-4"}],["path",{d:"M2 12h20A10 10 0 1 1 12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 4-10"}]],Np=[["path",{d:"M15.5 10 13 7.5 15.5 5"}],["path",{d:"M15.861 14A14.5 14.5 0 0112 22a14.48 14.48 0 010-20 10 10 0 109.888 11.5"}],["path",{d:"M19.5 5 22 7.5 19.5 10"}],["path",{d:"M2 12h8.5"}]],Kp=[["path",{d:"M15.686 15A14.5 14.5 0 0 1 12 22a14.5 14.5 0 0 1 0-20 10 10 0 1 0 9.542 13"}],["path",{d:"M2 12h8.5"}],["path",{d:"M20 6V4a2 2 0 1 0-4 0v2"}],["rect",{width:"8",height:"5",x:"14",y:"6",rx:"1"}]],Jp=[["path",{d:"M10.114 4.462A14.5 14.5 0 0 1 12 2a10 10 0 0 1 9.313 13.643"}],["path",{d:"M15.557 15.556A14.5 14.5 0 0 1 12 22 10 10 0 0 1 4.929 4.929"}],["path",{d:"M15.892 10.234A14.5 14.5 0 0 0 12 2a10 10 0 0 0-3.643.687"}],["path",{d:"M17.656 12H22"}],["path",{d:"M19.071 19.071A10 10 0 0 1 12 22 14.5 14.5 0 0 1 8.44 8.45"}],["path",{d:"M2 12h10"}],["path",{d:"m2 2 20 20"}]],Qp=[["path",{d:"m16 3 5 5"}],["path",{d:"M2 12h20A10 10 0 1 1 12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 4-10"}],["path",{d:"m21 3-5 5"}]],jp=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"}],["path",{d:"M2 12h20"}]],Yp=[["path",{d:"M12 13V2l8 4-8 4"}],["path",{d:"M20.561 10.222a9 9 0 1 1-12.55-5.29"}],["path",{d:"M8.002 9.997a5 5 0 1 0 8.9 2.02"}]],_p=[["path",{d:"M2 17h18a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H2"}],["path",{d:"M2 21V3"}],["path",{d:"M7 17v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-3"}],["circle",{cx:"16",cy:"11",r:"2"}],["circle",{cx:"8",cy:"11",r:"2"}]],xp=[["path",{d:"M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z"}],["path",{d:"M22 10v6"}],["path",{d:"M6 12.5V16a6 3 0 0 0 12 0v-3.5"}]],al=[["path",{d:"M22 5V2l-5.89 5.89"}],["circle",{cx:"16.6",cy:"15.89",r:"3"}],["circle",{cx:"8.11",cy:"7.4",r:"3"}],["circle",{cx:"12.35",cy:"11.65",r:"3"}],["circle",{cx:"13.91",cy:"5.85",r:"3"}],["circle",{cx:"18.15",cy:"10.09",r:"3"}],["circle",{cx:"6.56",cy:"13.2",r:"3"}],["circle",{cx:"10.8",cy:"17.44",r:"3"}],["circle",{cx:"5",cy:"19",r:"3"}]],hl=[["circle",{cx:"12",cy:"7",r:"4"}],["circle",{cx:"4",cy:"19",r:"2"}],["circle",{cx:"12",cy:"19",r:"2"}],["circle",{cx:"20",cy:"19",r:"2"}],["path",{d:"M8 7a4 4 0 0 0-4 4v6"}],["path",{d:"M12 11v6"}],["path",{d:"M16 7a4 4 0 0 1 4 4v6"}]],S2=[["path",{d:"M12 3v17a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6a1 1 0 0 1-1 1H3"}],["path",{d:"m16 19 2 2 4-4"}]],tl=[["path",{d:"m12.342 9.316 1.764-3.527"}],["path",{d:"m12.59 14.545 2.35 3.759"}],["path",{d:"m13.982 12.332 4.03.447"}],["path",{d:"M5.414 17.586 8.88 14.12"}],["path",{d:"M9.025 9.742 5.317 5.505"}],["circle",{cx:"11",cy:"12",r:"3"}],["circle",{cx:"15",cy:"4",r:"2"}],["circle",{cx:"16",cy:"20",r:"2"}],["circle",{cx:"20",cy:"13",r:"2"}],["circle",{cx:"4",cy:"19",r:"2"}],["circle",{cx:"4",cy:"4",r:"2"}]],w2=[["path",{d:"M12 3v17a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6a1 1 0 0 1-1 1H3"}],["path",{d:"M16 19h6"}],["path",{d:"M19 22v-6"}]],L2=[["path",{d:"M12 3v17a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6a1 1 0 0 1-1 1H3"}],["path",{d:"m16.5 16.5 5 5"}],["path",{d:"m16.5 21.5 5-5"}]],f2=[["path",{d:"M12 3v18"}],["path",{d:"M3 12h18"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],dl=[["path",{d:"M15 3v18"}],["path",{d:"M3 12h18"}],["path",{d:"M9 3v18"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],o=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"M3 15h18"}],["path",{d:"M9 3v18"}],["path",{d:"M15 3v18"}]],cl=[["circle",{cx:"12",cy:"9",r:"1"}],["circle",{cx:"19",cy:"9",r:"1"}],["circle",{cx:"5",cy:"9",r:"1"}],["circle",{cx:"12",cy:"15",r:"1"}],["circle",{cx:"19",cy:"15",r:"1"}],["circle",{cx:"5",cy:"15",r:"1"}]],Ml=[["circle",{cx:"9",cy:"12",r:"1"}],["circle",{cx:"9",cy:"5",r:"1"}],["circle",{cx:"9",cy:"19",r:"1"}],["circle",{cx:"15",cy:"12",r:"1"}],["circle",{cx:"15",cy:"5",r:"1"}],["circle",{cx:"15",cy:"19",r:"1"}]],pl=[["circle",{cx:"12",cy:"5",r:"1"}],["circle",{cx:"19",cy:"5",r:"1"}],["circle",{cx:"5",cy:"5",r:"1"}],["circle",{cx:"12",cy:"12",r:"1"}],["circle",{cx:"19",cy:"12",r:"1"}],["circle",{cx:"5",cy:"12",r:"1"}],["circle",{cx:"12",cy:"19",r:"1"}],["circle",{cx:"19",cy:"19",r:"1"}],["circle",{cx:"5",cy:"19",r:"1"}]],ll=[["path",{d:"M10 15h4"}],["path",{d:"M19.424 11.095a2.5 2.5 0 00.471-2.309 2 2 0 00.424-3.286 2 2 0 00-2.143-3.322 2 2 0 00-.676.502 2 2 0 00-3.287.424 2.5 2.5 0 00-3.189 2.06A3 3 0 0012 11l4-4"}],["path",{d:"M4 12.006A1 1 0 014.994 11H19a1 1 0 011 1v7a2 2 0 01-2 2H6a2 2 0 01-2-2z"}],["path",{d:"M7 11a4 4 0 01-4-4V5a1 1 0 011-1h2a4 4 0 013.584 2.222"}]],il=[["path",{d:"M3 7V5c0-1.1.9-2 2-2h2"}],["path",{d:"M17 3h2c1.1 0 2 .9 2 2v2"}],["path",{d:"M21 17v2c0 1.1-.9 2-2 2h-2"}],["path",{d:"M7 21H5c-1.1 0-2-.9-2-2v-2"}],["rect",{width:"7",height:"5",x:"7",y:"7",rx:"1"}],["rect",{width:"7",height:"5",x:"10",y:"12",rx:"1"}]],nl=[["path",{d:"m11.9 12.1 4.514-4.514"}],["path",{d:"M20.1 2.3a1 1 0 0 0-1.4 0l-1.114 1.114A2 2 0 0 0 17 4.828v1.344a2 2 0 0 1-.586 1.414A2 2 0 0 1 17.828 7h1.344a2 2 0 0 0 1.414-.586L21.7 5.3a1 1 0 0 0 0-1.4z"}],["path",{d:"m6 16 2 2"}],["path",{d:"M8.23 9.85A3 3 0 0 1 11 8a5 5 0 0 1 5 5 3 3 0 0 1-1.85 2.77l-.92.38A2 2 0 0 0 12 18a4 4 0 0 1-4 4 6 6 0 0 1-6-6 4 4 0 0 1 4-4 2 2 0 0 0 1.85-1.23z"}]],el=[["path",{d:"m12.172 11.828 4.242-4.242"}],["path",{d:"M16.414 7.586A2 2 0 0017 6.172V5a2 2 0 011.177-1.823L20.59 2.09a1 1 0 011.32 1.32l-1.088 2.412A2 2 0 0119 7h-1a2 2 0 00-1.586.586"}],["path",{d:"m6 16 2 2"}],["path",{d:"M8.23 9.85a3 3 0 011.28-1.454 1 1 0 011.496.815 4 4 0 003.783 3.783 1 1 0 01.815 1.496 3 3 0 01-1.454 1.28l-.92.38A2 2 0 0012 18a4 4 0 01-4 4 6 6 0 01-6-6 4 4 0 014-4 2 2 0 001.85-1.23z"}]],k2=[["circle",{cx:"12",cy:"18",r:"4"}],["path",{d:"M16 18V2"}]],rl=[["path",{d:"M13.144 21.144A7.274 10.445 45 1 0 2.856 10.856"}],["path",{d:"M13.144 21.144A7.274 4.365 45 0 0 2.856 10.856a7.274 4.365 45 0 0 10.288 10.288"}],["path",{d:"M16.565 10.435 18.6 8.4a2.501 2.501 0 1 0 1.65-4.65 2.5 2.5 0 1 0-4.66 1.66l-2.024 2.025"}],["path",{d:"m8.5 16.5-1-1"}]],ol=[["path",{d:"M12 16H4a2 2 0 1 1 0-4h16a2 2 0 1 1 0 4h-4.25"}],["path",{d:"M5 12a2 2 0 0 1-2-2 9 7 0 0 1 18 0 2 2 0 0 1-2 2"}],["path",{d:"M5 16a2 2 0 0 0-2 2 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 2 2 0 0 0-2-2q0 0 0 0"}],["path",{d:"m6.67 12 6.13 4.6a2 2 0 0 0 2.8-.4l3.15-4.2"}]],vl=[["path",{d:"m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9"}],["path",{d:"m18 15 4-4"}],["path",{d:"m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172v-.344a2 2 0 0 0-.586-1.414l-1.657-1.657A6 6 0 0 0 12.516 3H9l1.243 1.243A6 6 0 0 1 12 8.485V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5"}]],$l=[["path",{d:"M11 15h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 17"}],["path",{d:"m7 21 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"}],["path",{d:"m2 16 6 6"}],["circle",{cx:"16",cy:"9",r:"2.9"}],["circle",{cx:"6",cy:"5",r:"3"}]],ml=[["path",{d:"M12.035 17.012a3 3 0 0 0-3-3l-.311-.002a.72.72 0 0 1-.505-1.229l1.195-1.195A2 2 0 0 1 10.828 11H12a2 2 0 0 0 0-4H9.243a3 3 0 0 0-2.122.879l-2.707 2.707A4.83 4.83 0 0 0 3 14a8 8 0 0 0 8 8h2a8 8 0 0 0 8-8V7a2 2 0 1 0-4 0v2a2 2 0 1 0 4 0"}],["path",{d:"M13.888 9.662A2 2 0 0 0 17 8V5A2 2 0 1 0 13 5"}],["path",{d:"M9 5A2 2 0 1 0 5 5V10"}],["path",{d:"M9 7V4A2 2 0 1 1 13 4V7.268"}]],P2=[["path",{d:"M18 11.5V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1.4"}],["path",{d:"M14 10V8a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2"}],["path",{d:"M10 9.9V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v5"}],["path",{d:"M6 14a2 2 0 0 0-2-2a2 2 0 0 0-2 2"}],["path",{d:"M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-4a8 8 0 0 1-8-8 2 2 0 1 1 4 0"}]],yl=[["path",{d:"M11 14h2a2 2 0 0 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 16"}],["path",{d:"m14.45 13.39 5.05-4.694C20.196 8 21 6.85 21 5.75a2.75 2.75 0 0 0-4.797-1.837.276.276 0 0 1-.406 0A2.75 2.75 0 0 0 11 5.75c0 1.2.802 2.248 1.5 2.946L16 11.95"}],["path",{d:"m2 15 6 6"}],["path",{d:"m7 20 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a1 1 0 0 0-2.75-2.91"}]],B2=[["path",{d:"M11 12h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 14"}],["path",{d:"m7 18 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"}],["path",{d:"m2 13 6 6"}]],sl=[["path",{d:"M18 12.5V10a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1.4"}],["path",{d:"M14 11V9a2 2 0 1 0-4 0v2"}],["path",{d:"M10 10.5V5a2 2 0 1 0-4 0v9"}],["path",{d:"m7 15-1.76-1.76a2 2 0 0 0-2.83 2.82l3.6 3.6C7.5 21.14 9.2 22 12 22h2a8 8 0 0 0 8-8V7a2 2 0 1 0-4 0v5"}]],Cl=[["path",{d:"M12 3V2"}],["path",{d:"m15.4 17.4 3.2-2.8a2 2 0 1 1 2.8 2.9l-3.6 3.3c-.7.8-1.7 1.2-2.8 1.2h-4c-1.1 0-2.1-.4-2.8-1.2l-1.302-1.464A1 1 0 0 0 6.151 19H5"}],["path",{d:"M2 14h12a2 2 0 0 1 0 4h-2"}],["path",{d:"M4 10h16"}],["path",{d:"M5 10a7 7 0 0 1 14 0"}],["path",{d:"M5 14v6a1 1 0 0 1-1 1H2"}]],ul=[["path",{d:"M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2"}],["path",{d:"M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2"}],["path",{d:"M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8"}],["path",{d:"M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"}]],gl=[["path",{d:"M2.048 18.566A2 2 0 0 0 4 21h16a2 2 0 0 0 1.952-2.434l-2-9A2 2 0 0 0 18 8H6a2 2 0 0 0-1.952 1.566z"}],["path",{d:"M8 11V6a4 4 0 0 1 8 0v5"}]],Al=[["path",{d:"M21 8 8 21"}],["path",{d:"m21 15-6 6"}]],Hl=[["path",{d:"m11 17 2 2a1 1 0 1 0 3-3"}],["path",{d:"m14 14 2.5 2.5a1 1 0 1 0 3-3l-3.88-3.88a3 3 0 0 0-4.24 0l-.88.88a1 1 0 1 1-3-3l2.81-2.81a5.79 5.79 0 0 1 7.06-.87l.47.28a2 2 0 0 0 1.42.25L21 4"}],["path",{d:"m21 3 1 11h-2"}],["path",{d:"M3 3 2 14l6.5 6.5a1 1 0 1 0 3-3"}],["path",{d:"M3 4h8"}]],Vl=[["path",{d:"M12 2v8"}],["path",{d:"m16 6-4 4-4-4"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2"}],["path",{d:"M6 18h.01"}],["path",{d:"M10 18h.01"}]],Sl=[["path",{d:"m16 6-4-4-4 4"}],["path",{d:"M12 2v8"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2"}],["path",{d:"M6 18h.01"}],["path",{d:"M10 18h.01"}]],wl=[["path",{d:"M10 16h.01"}],["path",{d:"M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"}],["path",{d:"M21.946 12.013H2.054"}],["path",{d:"M6 16h.01"}]],Ll=[["path",{d:"M10 10V5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v5"}],["path",{d:"M14 6a6 6 0 0 1 6 6v3"}],["path",{d:"M4 15v-3a6 6 0 0 1 6-6"}],["rect",{x:"2",y:"15",width:"20",height:"4",rx:"1"}]],fl=[["line",{x1:"4",x2:"20",y1:"9",y2:"9"}],["line",{x1:"4",x2:"20",y1:"15",y2:"15"}],["line",{x1:"10",x2:"8",y1:"3",y2:"21"}],["line",{x1:"16",x2:"14",y1:"3",y2:"21"}]],kl=[["path",{d:"M14 18a2 2 0 0 0-4 0"}],["path",{d:"m19 11-2.11-6.657a2 2 0 0 0-2.752-1.148l-1.276.61A2 2 0 0 1 12 4H8.5a2 2 0 0 0-1.925 1.456L5 11"}],["path",{d:"M2 11h20"}],["circle",{cx:"17",cy:"18",r:"3"}],["circle",{cx:"7",cy:"18",r:"3"}]],Pl=[["path",{d:"M10 12H6"}],["path",{d:"M10 15V9"}],["path",{d:"M14 14.5a.5.5 0 0 0 .5.5h1a2.5 2.5 0 0 0 2.5-2.5v-1A2.5 2.5 0 0 0 15.5 9h-1a.5.5 0 0 0-.5.5z"}],["path",{d:"M6 15V9"}],["rect",{x:"2",y:"5",width:"20",height:"14",rx:"2"}]],Bl=[["path",{d:"m5.2 6.2 1.4 1.4"}],["path",{d:"M2 13h2"}],["path",{d:"M20 13h2"}],["path",{d:"m17.4 7.6 1.4-1.4"}],["path",{d:"M22 17H2"}],["path",{d:"M22 21H2"}],["path",{d:"M16 13a4 4 0 0 0-8 0"}],["path",{d:"M12 5V2.5"}]],zl=[["path",{d:"M22 9a1 1 0 00-1-1H3a1 1 0 00-1 1v4a1 1 0 001 1h.5a2 2 0 011.6.8l.3.4A2 2 0 007 16h10a2 2 0 001.6-.8l.3-.4a2 2 0 011.6-.8h.5a1 1 0 001-1z"}],["path",{d:"M8 12h8"}]],Dl=[["path",{d:"M4 12h8"}],["path",{d:"M4 18V6"}],["path",{d:"M12 18V6"}],["path",{d:"m17 12 3-2v8"}]],Fl=[["path",{d:"M4 12h8"}],["path",{d:"M4 18V6"}],["path",{d:"M12 18V6"}],["path",{d:"M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1"}]],Rl=[["path",{d:"M4 12h8"}],["path",{d:"M4 18V6"}],["path",{d:"M12 18V6"}],["path",{d:"M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2"}],["path",{d:"M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2"}]],bl=[["path",{d:"M12 18V6"}],["path",{d:"M17 10v3a1 1 0 0 0 1 1h3"}],["path",{d:"M21 10v8"}],["path",{d:"M4 12h8"}],["path",{d:"M4 18V6"}]],Tl=[["path",{d:"M4 12h8"}],["path",{d:"M4 18V6"}],["path",{d:"M12 18V6"}],["path",{d:"M17 13v-3h4"}],["path",{d:"M17 17.7c.4.2.8.3 1.3.3 1.5 0 2.7-1.1 2.7-2.5S19.8 13 18.3 13H17"}]],ql=[["path",{d:"M4 12h8"}],["path",{d:"M4 18V6"}],["path",{d:"M12 18V6"}],["circle",{cx:"19",cy:"16",r:"2"}],["path",{d:"M20 10c-2 2-3 3.5-3 6"}]],Ul=[["path",{d:"M6 12h12"}],["path",{d:"M6 20V4"}],["path",{d:"M18 20V4"}]],Ol=[["path",{d:"M21 14h-1.343"}],["path",{d:"M9.128 3.47A9 9 0 0 1 21 12v3.343"}],["path",{d:"m2 2 20 20"}],["path",{d:"M20.414 20.414A2 2 0 0 1 19 21h-1a2 2 0 0 1-2-2v-3"}],["path",{d:"M3 14h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a9 9 0 0 1 2.636-6.364"}]],Gl=[["path",{d:"M3 14h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a9 9 0 0 1 18 0v7a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3"}]],Zl=[["path",{d:"M3 11h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5Zm0 0a9 9 0 1 1 18 0m0 0v5a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3Z"}],["path",{d:"M21 16v2a4 4 0 0 1-4 4h-5"}]],El=[["path",{d:"M12.409 5.824c-.702.792-1.15 1.496-1.415 2.166l2.153 2.156a.5.5 0 0 1 0 .707l-2.293 2.293a.5.5 0 0 0 0 .707L12 15"}],["path",{d:"M13.508 20.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5a5.5 5.5 0 0 1 9.591-3.677.6.6 0 0 0 .818.001A5.5 5.5 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5z"}]],Wl=[["path",{d:"M19.414 14.414C21 12.828 22 11.5 22 9.5a5.5 5.5 0 0 0-9.591-3.676.6.6 0 0 1-.818.001A5.5 5.5 0 0 0 2 9.5c0 2.3 1.5 4 3 5.5l5.535 5.362a2 2 0 0 0 2.879.052 2.12 2.12 0 0 0-.004-3 2.124 2.124 0 1 0 3-3 2.124 2.124 0 0 0 3.004 0 2 2 0 0 0 0-2.828l-1.881-1.882a2.41 2.41 0 0 0-3.409 0l-1.71 1.71a2 2 0 0 1-2.828 0 2 2 0 0 1 0-2.828l2.823-2.762"}]],Il=[["path",{d:"m14.876 18.99-1.368 1.323a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5a5.2 5.2 0 0 1-.244 1.572"}],["path",{d:"M15 15h6"}]],Xl=[["path",{d:"M10.5 4.893a5.5 5.5 0 0 1 1.091.931.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 1.872-1.002 3.356-2.187 4.655"}],["path",{d:"m16.967 16.967-3.459 3.346a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5a5.5 5.5 0 0 1 2.747-4.761"}],["path",{d:"m2 2 20 20"}]],Nl=[["path",{d:"m14.479 19.374-.971.939a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5a5.2 5.2 0 0 1-.219 1.49"}],["path",{d:"M15 15h6"}],["path",{d:"M18 12v6"}]],Kl=[["path",{d:"M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"}],["path",{d:"M3.22 13H9.5l.5-1 2 4.5 2-7 1.5 3.5h5.27"}]],Jl=[["path",{d:"m15.5 12.5 5 5"}],["path",{d:"m20.5 12.5-5 5"}],["path",{d:"M21.955 8.774a5.5 5.5 0 0 0-9.546-2.95.6.6 0 0 1-.818 0A5.5 5.5 0 0 0 2 9.5c0 2.3 1.5 4 3 5.5l5.508 5.332a2 2 0 0 0 2.57.352"}]],Ql=[["path",{d:"M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"}]],jl=[["path",{d:"M11 8c2-3-2-3 0-6"}],["path",{d:"M15.5 8c2-3-2-3 0-6"}],["path",{d:"M6 10h.01"}],["path",{d:"M6 14h.01"}],["path",{d:"M10 16v-4"}],["path",{d:"M14 16v-4"}],["path",{d:"M18 16v-4"}],["path",{d:"M20 6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3"}],["path",{d:"M5 20v2"}],["path",{d:"M19 20v2"}]],Yl=[["path",{d:"M11 17v4"}],["path",{d:"M14 3v8a2 2 0 0 0 2 2h5.865"}],["path",{d:"M17 17v4"}],["path",{d:"M18 17a4 4 0 0 0 4-4 8 6 0 0 0-8-6 6 5 0 0 0-6 5v3a2 2 0 0 0 2 2z"}],["path",{d:"M2 10v5"}],["path",{d:"M6 3h16"}],["path",{d:"M7 21h14"}],["path",{d:"M8 13H2"}]],_l=[["path",{d:"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"}]],xl=[["path",{d:"m9 11-6 6v3h9l3-3"}],["path",{d:"m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4"}]],ai=[["path",{d:"M13.5 10.5 2 22"}],["path",{d:"M16.352 11.648a1.205 1.205 0 01-1.704 0l-2.296-2.296a1.205 1.205 0 010-1.704l.72-.72a2 2 0 011.022-.546l.599-.12a2 2 0 001.569-1.57l.12-.598a2 2 0 01.546-1.022l.72-.72a1.205 1.205 0 011.704 0l2.296 2.296a1.205 1.205 0 010 1.704l-6.02 6.02a1 1 0 103 3l.201-.201A7.4 7.4 0 0021 9.93V7"}],["path",{d:"m6 21-3-3"}]],hi=[["path",{d:"M10.82 16.12c1.69.6 3.91.79 5.18.85.55.03 1-.42.97-.97-.06-1.27-.26-3.5-.85-5.18"}],["path",{d:"M11.5 6.5c1.64 0 5-.38 6.71-1.07.52-.2.55-.82.12-1.17A10 10 0 0 0 4.26 18.33c.35.43.96.4 1.17-.12.69-1.71 1.07-5.07 1.07-6.71 1.34.45 3.1.9 4.88.62a.88.88 0 0 0 .73-.74c.3-2.14-.15-3.5-.61-4.88"}],["path",{d:"M15.62 16.95c.2.85.62 2.76.5 4.28a.77.77 0 0 1-.9.7 16.64 16.64 0 0 1-4.08-1.36"}],["path",{d:"M16.13 21.05c1.65.63 3.68.84 4.87.91a.9.9 0 0 0 .96-.96 17.68 17.68 0 0 0-.9-4.87"}],["path",{d:"M16.94 15.62c.86.2 2.77.62 4.29.5a.77.77 0 0 0 .7-.9 16.64 16.64 0 0 0-1.36-4.08"}],["path",{d:"M17.99 5.52a20.82 20.82 0 0 1 3.15 4.5.8.8 0 0 1-.68 1.13c-2.33.2-5.3-.32-8.27-1.57"}],["path",{d:"M4.93 4.93 3 3a.7.7 0 0 1 0-1"}],["path",{d:"M9.58 12.18c1.24 2.98 1.77 5.95 1.57 8.28a.8.8 0 0 1-1.13.68 20.82 20.82 0 0 1-4.5-3.15"}]],ti=[["path",{d:"M10.82 16.12c1.69.6 3.91.79 5.18.85.28.01.53-.09.7-.27"}],["path",{d:"M11.14 20.57c.52.24 2.44 1.12 4.08 1.37.46.06.86-.25.9-.71.12-1.52-.3-3.43-.5-4.28"}],["path",{d:"M16.13 21.05c1.65.63 3.68.84 4.87.91a.9.9 0 0 0 .7-.26"}],["path",{d:"M17.99 5.52a20.83 20.83 0 0 1 3.15 4.5.8.8 0 0 1-.68 1.13c-1.17.1-2.5.02-3.9-.25"}],["path",{d:"M20.57 11.14c.24.52 1.12 2.44 1.37 4.08.04.3-.08.59-.31.75"}],["path",{d:"M4.93 4.93a10 10 0 0 0-.67 13.4c.35.43.96.4 1.17-.12.69-1.71 1.07-5.07 1.07-6.71 1.34.45 3.1.9 4.88.62a.85.85 0 0 0 .48-.24"}],["path",{d:"M5.52 17.99c1.05.95 2.91 2.42 4.5 3.15a.8.8 0 0 0 1.13-.68c.2-2.34-.33-5.3-1.57-8.28"}],["path",{d:"M8.35 2.68a10 10 0 0 1 9.98 1.58c.43.35.4.96-.12 1.17-1.5.6-4.3.98-6.07 1.05"}],["path",{d:"m2 2 20 20"}]],di=[["path",{d:"M12 7v4"}],["path",{d:"M14 21v-3a2 2 0 0 0-4 0v3"}],["path",{d:"M14 9h-4"}],["path",{d:"M18 11h2a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-9a2 2 0 0 1 2-2h2"}],["path",{d:"M18 21V5a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16"}]],ci=[["path",{d:"M10 22v-6.57"}],["path",{d:"M12 11h.01"}],["path",{d:"M12 7h.01"}],["path",{d:"M14 15.43V22"}],["path",{d:"M15 16a5 5 0 0 0-6 0"}],["path",{d:"M16 11h.01"}],["path",{d:"M16 7h.01"}],["path",{d:"M8 11h.01"}],["path",{d:"M8 7h.01"}],["rect",{x:"4",y:"2",width:"16",height:"20",rx:"2"}]],Mi=[["path",{d:"m14.305 19.53.923-.382"}],["path",{d:"m15.228 16.852-.923-.383"}],["path",{d:"m16.852 15.228-.383-.923"}],["path",{d:"m16.852 20.772-.383.924"}],["path",{d:"M17 2v4.172a2 2 0 0 1-.586 1.414l-8.828 8.828A2 2 0 0 0 7 17.828V22"}],["path",{d:"m19.148 15.228.383-.923"}],["path",{d:"m19.53 21.696-.382-.924"}],["path",{d:"m20.772 16.852.924-.383"}],["path",{d:"m20.772 19.148.924.383"}],["path",{d:"M5 22h6.159"}],["path",{d:"M5 2h14"}],["path",{d:"M7 2v4.172a2 2 0 0 0 .586 1.414l5.188 5.188"}],["circle",{cx:"18",cy:"18",r:"3"}]],pi=[["path",{d:"M5 22h14"}],["path",{d:"M5 2h14"}],["path",{d:"M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22"}],["path",{d:"M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"}]],li=[["path",{d:"M10.584 21H5a2 2 0 01-2-2v-9a2 2 0 01.709-1.527l7-6a2 2 0 012.582 0l7 6A2 2 0 0121 10.001v.583"}],["path",{d:"M14 12H10a1 1 0 00-1 1v8"}],["path",{d:"m14.305 19.53.923-.382"}],["path",{d:"m15.229 16.852-.924-.383"}],["path",{d:"m16.852 15.228-.383-.923"}],["path",{d:"m16.852 20.773-.383.924"}],["path",{d:"m19.148 15.228.383-.923"}],["path",{d:"m19.53 21.697-.382-.924"}],["path",{d:"m20.773 16.852.922-.383"}],["path",{d:"m20.773 19.148.922.383"}],["circle",{cx:"18",cy:"18",r:"3"}]],ii=[["path",{d:"M8.62 13.8A2.25 2.25 0 1 1 12 10.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a.998.998 0 0 1-1.507 0z"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"}]],ni=[["path",{d:"M12.35 21H5a2 2 0 0 1-2-2v-9a2 2 0 0 1 .71-1.53l7-6a2 2 0 0 1 2.58 0l7 6A2 2 0 0 1 21 10v2.35"}],["path",{d:"M14.8 12.4A1 1 0 0 0 14 12h-4a1 1 0 0 0-1 1v8"}],["path",{d:"M15 18h6"}],["path",{d:"M18 15v6"}]],ei=[["path",{d:"M10 12V8.964"}],["path",{d:"M14 12V8.964"}],["path",{d:"M15 12a1 1 0 0 1 1 1v2a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-2a1 1 0 0 1 1-1z"}],["path",{d:"M8.5 21H5a2 2 0 0 1-2-2v-9a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-2"}]],ri=[["path",{d:"M9.5 13.866a4 4 0 0 1 5 .01"}],["path",{d:"M12 17h.01"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"}],["path",{d:"M7 10.754a8 8 0 0 1 10 0"}]],z2=[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"}]],oi=[["path",{d:"m12.681 4.24.834-.715a1.45 1.45 0 011.88 0l5.09 4.364A1.45 1.45 0 0121 9v6.546a1.45 1.45 0 01-1 1.381"}],["path",{d:"M15.485 11.889A1.45 1.45 0 0116 13v6.546A1.454 1.454 0 0114.546 21H4.364a1.454 1.454 0 01-1.454-1.454V13a1.45 1.45 0 01.515-1.111l5.09-4.364a1.45 1.45 0 011.88 0z"}],["path",{d:"M7.41 20.546v-4a1 1 0 011-1h2a1 1 0 011 1v4"}]],D2=[["path",{d:"M12 17c5 0 8-2.69 8-6H4c0 3.31 3 6 8 6m-4 4h8m-4-3v3M5.14 11a3.5 3.5 0 1 1 6.71 0"}],["path",{d:"M12.14 11a3.5 3.5 0 1 1 6.71 0"}],["path",{d:"M15.5 6.5a3.5 3.5 0 1 0-7 0"}]],F2=[["path",{d:"m7 11 4.08 10.35a1 1 0 0 0 1.84 0L17 11"}],["path",{d:"M17 7A5 5 0 0 0 7 7"}],["path",{d:"M17 7a2 2 0 0 1 0 4H7a2 2 0 0 1 0-4"}]],vi=[["path",{d:"M13.5 8h-3"}],["path",{d:"m15 2-1 2h3a2 2 0 012 2v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6a2 2 0 012-2h3"}],["path",{d:"M16 22a4 4 0 00-8 0"}],["path",{d:"m9 2 3 6"}],["circle",{cx:"12",cy:"15",r:"3"}]],$i=[["path",{d:"M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21"}],["path",{d:"m14 19 3 3v-5.5"}],["path",{d:"m17 22 3-3"}],["circle",{cx:"9",cy:"9",r:"2"}]],mi=[["path",{d:"M21 9v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"}],["line",{x1:"16",x2:"22",y1:"5",y2:"5"}],["circle",{cx:"9",cy:"9",r:"2"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"}]],yi=[["line",{x1:"2",x2:"22",y1:"2",y2:"22"}],["path",{d:"M10.41 10.41a2 2 0 1 1-2.83-2.83"}],["line",{x1:"13.5",x2:"6",y1:"13.5",y2:"21"}],["line",{x1:"18",x2:"21",y1:"12",y2:"15"}],["path",{d:"M3.59 3.59A1.99 1.99 0 0 0 3 5v14a2 2 0 0 0 2 2h14c.55 0 1.052-.22 1.41-.59"}],["path",{d:"M21 15V5a2 2 0 0 0-2-2H9"}]],si=[["path",{d:"M13 19a4 4 0 00-8 0"}],["path",{d:"M16 10h2"}],["path",{d:"M16 14h2"}],["circle",{cx:"9",cy:"12",r:"3"}],["rect",{x:"2",y:"5",width:"20",height:"14",rx:"2"}]],Ci=[["path",{d:"M15 15.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"}],["path",{d:"M21 12.17V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6"}],["path",{d:"m6 21 5-5"}],["circle",{cx:"9",cy:"9",r:"2"}]],ui=[["path",{d:"M16 5h6"}],["path",{d:"M19 2v6"}],["path",{d:"M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"}],["circle",{cx:"9",cy:"9",r:"2"}]],gi=[["path",{d:"M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21"}],["path",{d:"m14 19.5 3-3 3 3"}],["path",{d:"M17 22v-5.5"}],["circle",{cx:"9",cy:"9",r:"2"}]],Ai=[["path",{d:"M16 3h5v5"}],["path",{d:"M17 21h2a2 2 0 0 0 2-2"}],["path",{d:"M21 12v3"}],["path",{d:"m21 3-5 5"}],["path",{d:"M3 7V5a2 2 0 0 1 2-2"}],["path",{d:"m5 21 4.144-4.144a1.21 1.21 0 0 1 1.712 0L13 19"}],["path",{d:"M9 3h3"}],["rect",{x:"3",y:"11",width:"10",height:"10",rx:"1"}]],Hi=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["circle",{cx:"9",cy:"9",r:"2"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"}]],Vi=[["path",{d:"m22 11-1.296-1.296a2.4 2.4 0 0 0-3.408 0L11 16"}],["path",{d:"M4 8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2"}],["circle",{cx:"13",cy:"7",r:"1",fill:"currentColor"}],["rect",{x:"8",y:"2",width:"14",height:"14",rx:"2"}]],Si=[["path",{d:"M12 3v12"}],["path",{d:"m8 11 4 4 4-4"}],["path",{d:"M8 5H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4"}]],wi=[["path",{d:"M6 3h12"}],["path",{d:"M6 8h12"}],["path",{d:"m6 13 8.5 8"}],["path",{d:"M6 13h3"}],["path",{d:"M9 13c6.667 0 6.667-10 0-10"}]],Li=[["path",{d:"M6 16c5 0 7-8 12-8a4 4 0 0 1 0 8c-5 0-7-8-12-8a4 4 0 1 0 0 8"}]],fi=[["polyline",{points:"22 12 16 12 14 15 10 15 8 12 2 12"}],["path",{d:"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"}]],ki=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 16v-4"}],["path",{d:"M12 8h.01"}]],Pi=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M7 7h.01"}],["path",{d:"M17 7h.01"}],["path",{d:"M7 17h.01"}],["path",{d:"M17 17h.01"}]],Bi=[["line",{x1:"19",x2:"10",y1:"4",y2:"4"}],["line",{x1:"14",x2:"5",y1:"20",y2:"20"}],["line",{x1:"15",x2:"9",y1:"4",y2:"20"}]],zi=[["path",{d:"m16 14 4 4-4 4"}],["path",{d:"M20 10a8 8 0 1 0-8 8h8"}]],Di=[["path",{d:"M12 18v2a2 2 0 002 2h6"}],["path",{d:"M6 11c.72.5 1.44 1 3 1 3 0 3-2 6-2 1.56 0 2.28.5 3 1"}],["path",{d:"M9.293 3c.453 0 .887-.18 1.207-.5s.754-.5 1.207-.5h.586c.453 0 .887.18 1.207.5s.754.5 1.207.5H16a2 2 0 012 2v11a2 2 0 01-2 2H8a2 2 0 01-2-2V5a2 2 0 012-2z"}]],Fi=[["path",{d:"M4 10a8 8 0 1 1 8 8H4"}],["path",{d:"m8 22-4-4 4-4"}]],Ri=[["path",{d:"M12 9.5V21m0-11.5L6 3m6 6.5L18 3"}],["path",{d:"M6 15h12"}],["path",{d:"M6 11h12"}]],bi=[["path",{d:"M21 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-2Z"}],["path",{d:"M6 15v-2"}],["path",{d:"M12 15V9"}],["circle",{cx:"12",cy:"6",r:"3"}]],Ti=[["path",{d:"M18 17a1 1 0 0 0-1 1v1a2 2 0 1 0 2-2z"}],["path",{d:"M20.97 3.61a.45.45 0 0 0-.58-.58C10.2 6.6 6.6 10.2 3.03 20.39a.45.45 0 0 0 .58.58C13.8 17.4 17.4 13.8 20.97 3.61"}],["path",{d:"m6.707 6.707 10.586 10.586"}],["path",{d:"M7 5a2 2 0 1 0-2 2h1a1 1 0 0 0 1-1z"}]],qi=[["path",{d:"M5 3v14"}],["path",{d:"M12 3v8"}],["path",{d:"M19 3v18"}]],Ui=[["path",{d:"M12 8v12"}],["path",{d:"M6 4h12"}],["path",{d:"M6 8h12"}]],Oi=[["path",{d:"M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"}],["circle",{cx:"16.5",cy:"7.5",r:".5",fill:"currentColor"}]],Gi=[["path",{d:"M12.4 2.7a2.5 2.5 0 0 1 3.4 0l5.5 5.5a2.5 2.5 0 0 1 0 3.4l-3.7 3.7a2.5 2.5 0 0 1-3.4 0L8.7 9.8a2.5 2.5 0 0 1 0-3.4z"}],["path",{d:"m14 7 3 3"}],["path",{d:"m9.4 10.6-6.814 6.814A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814"}]],Zi=[["path",{d:"m2 21 9.6-9.6"}],["path",{d:"m7.5 15.5 2.3 2.3a1 1 0 0 1 0 1.4l-2.1 2.1a1 1 0 0 1-1.4 0L4 19"}],["circle",{cx:"15.5",cy:"7.5",r:"5.5"}]],Ei=[["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2"}],["path",{d:"M6 8h4"}],["path",{d:"M14 8h.01"}],["path",{d:"M18 8h.01"}],["path",{d:"M2 12h20"}],["path",{d:"M6 12v4"}],["path",{d:"M10 12v4"}],["path",{d:"M14 12v4"}],["path",{d:"M18 12v4"}]],Wi=[["path",{d:"M10 8h.01"}],["path",{d:"M12 12h.01"}],["path",{d:"M14 8h.01"}],["path",{d:"M16 12h.01"}],["path",{d:"M18 8h.01"}],["path",{d:"M6 8h.01"}],["path",{d:"M7 16h10"}],["path",{d:"M8 12h.01"}],["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2"}]],Ii=[["path",{d:"M 20 4 A2 2 0 0 1 22 6"}],["path",{d:"M 22 6 L 22 16.41"}],["path",{d:"M 7 16 L 16 16"}],["path",{d:"M 9.69 4 L 20 4"}],["path",{d:"M14 8h.01"}],["path",{d:"M18 8h.01"}],["path",{d:"m2 2 20 20"}],["path",{d:"M20 20H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"}],["path",{d:"M6 8h.01"}],["path",{d:"M8 12h.01"}]],Xi=[["path",{d:"M11.38 10 5 20"}],["path",{d:"M19 18a2 2 0 01-2 2c-4.87-.003-5.052-16-10-16a2 2 0 00-2 2"}]],Ni=[["path",{d:"M12 2v5"}],["path",{d:"M14.829 15.998a3 3 0 1 1-5.658 0"}],["path",{d:"M20.92 14.606A1 1 0 0 1 20 16H4a1 1 0 0 1-.92-1.394l3-7A1 1 0 0 1 7 7h10a1 1 0 0 1 .92.606z"}]],Ki=[["path",{d:"M10.293 2.293a1 1 0 0 1 1.414 0l2.5 2.5 5.994 1.227a1 1 0 0 1 .506 1.687l-7 7a1 1 0 0 1-1.687-.506l-1.227-5.994-2.5-2.5a1 1 0 0 1 0-1.414z"}],["path",{d:"m14.207 4.793-3.414 3.414"}],["path",{d:"M3 20a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z"}],["path",{d:"m9.086 6.5-4.793 4.793a1 1 0 0 0-.18 1.17L7 18"}]],Ji=[["path",{d:"M12 10v12"}],["path",{d:"M17.929 7.629A1 1 0 0 1 17 9H7a1 1 0 0 1-.928-1.371l2-5A1 1 0 0 1 9 2h6a1 1 0 0 1 .928.629z"}],["path",{d:"M9 22h6"}]],Qi=[["path",{d:"M19.929 18.629A1 1 0 0 1 19 20H9a1 1 0 0 1-.928-1.371l2-5A1 1 0 0 1 11 13h6a1 1 0 0 1 .928.629z"}],["path",{d:"M6 3a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"}],["path",{d:"M8 6h4a2 2 0 0 1 2 2v5"}]],ji=[["path",{d:"M19.929 9.629A1 1 0 0 1 19 11H9a1 1 0 0 1-.928-1.371l2-5A1 1 0 0 1 11 4h6a1 1 0 0 1 .928.629z"}],["path",{d:"M6 15a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1z"}],["path",{d:"M8 18h4a2 2 0 0 0 2-2v-5"}]],Yi=[["path",{d:"M12 12v6"}],["path",{d:"M4.077 10.615A1 1 0 0 0 5 12h14a1 1 0 0 0 .923-1.385l-3.077-7.384A2 2 0 0 0 15 2H9a2 2 0 0 0-1.846 1.23Z"}],["path",{d:"M8 20a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1z"}]],_i=[["path",{d:"m12 8 6-3-6-3v10"}],["path",{d:"m8 11.99-5.5 3.14a1 1 0 0 0 0 1.74l8.5 4.86a2 2 0 0 0 2 0l8.5-4.86a1 1 0 0 0 0-1.74L16 12"}],["path",{d:"m6.49 12.85 11.02 6.3"}],["path",{d:"M17.51 12.85 6.5 19.15"}]],xi=[["path",{d:"M10 18v-7"}],["path",{d:"M11.119 2.205a2 2 0 0 1 1.762 0l7.84 3.846A.5.5 0 0 1 20.5 7h-17a.5.5 0 0 1-.22-.949z"}],["path",{d:"M14 18v-7"}],["path",{d:"M18 18v-7"}],["path",{d:"M3 22h18"}],["path",{d:"M6 18v-7"}]],an=[["path",{d:"m5 8 6 6"}],["path",{d:"m4 14 6-6 2-3"}],["path",{d:"M2 5h12"}],["path",{d:"M7 2h1"}],["path",{d:"m22 22-5-10-5 10"}],["path",{d:"M14 18h6"}]],hn=[["path",{d:"M2 20h20"}],["path",{d:"m9 10 2 2 4-4"}],["rect",{x:"3",y:"4",width:"18",height:"12",rx:"2"}]],R2=[["rect",{width:"18",height:"12",x:"3",y:"4",rx:"2",ry:"2"}],["line",{x1:"2",x2:"22",y1:"20",y2:"20"}]],tn=[["path",{d:"M18 5a2 2 0 0 1 2 2v8.526a2 2 0 0 0 .212.897l1.068 2.127a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45l1.068-2.127A2 2 0 0 0 4 15.526V7a2 2 0 0 1 2-2z"}],["path",{d:"M20.054 15.987H3.946"}]],dn=[["path",{d:"M7 22a5 5 0 0 1-2-4"}],["path",{d:"M7 16.93c.96.43 1.96.74 2.99.91"}],["path",{d:"M3.34 14A6.8 6.8 0 0 1 2 10c0-4.42 4.48-8 10-8s10 3.58 10 8a7.19 7.19 0 0 1-.33 2"}],["path",{d:"M5 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"}],["path",{d:"M14.33 22h-.09a.35.35 0 0 1-.24-.32v-10a.34.34 0 0 1 .33-.34c.08 0 .15.03.21.08l7.34 6a.33.33 0 0 1-.21.59h-4.49l-2.57 3.85a.35.35 0 0 1-.28.14z"}]],cn=[["path",{d:"M12 10v10"}],["path",{d:"M22 10a1 1 0 01-.59.92l-5.077 2.308"}],["path",{d:"M22.017 10.005a1 1 0 00-.597-.916l-8.59-3.91a2 2 0 00-1.66.001L2.6 9.08a1 1 0 00-.02 1.831l5.093 2.316"}],["path",{d:"m9 17 3 3 3-3"}]],Mn=[["path",{d:"M3.704 14.467a10 8 0 1 1 3.115 2.375"}],["path",{d:"M7 22a5 5 0 0 1-2-3.994"}],["circle",{cx:"5",cy:"16",r:"2"}]],pn=[["path",{d:"M12 14V4"}],["path",{d:"M7.674 10.774 2.58 13.09a1 1 0 000 1.822l8.6 3.91a2 2 0 001.65 0l8.58-3.9a1 1 0 00.59-.92 1 1 0 00-.59-.922l-5.078-2.308"}],["path",{d:"m9 7 3-3 3 3"}]],ln=[["path",{d:"M13 13.74a2 2 0 0 1-2 0L2.5 8.87a1 1 0 0 1 0-1.74L11 2.26a2 2 0 0 1 2 0l8.5 4.87a1 1 0 0 1 0 1.74z"}],["path",{d:"m20 14.285 1.5.845a1 1 0 0 1 0 1.74L13 21.74a2 2 0 0 1-2 0l-8.5-4.87a1 1 0 0 1 0-1.74l1.5-.845"}]],nn=[["path",{d:"M12 7v15"}],["path",{d:"M2 12a1 1 0 00.58.91l5.093 2.316"}],["path",{d:"M22 12a1 1 0 01-.59.92l-5.077 2.308"}],["path",{d:"M8 10.37 2.6 7.91a1 1 0 010-1.831l8.57-3.9a2 2 0 011.66.001l8.59 3.91a1 1 0 010 1.831l-5.392 2.45"}],["path",{d:"m9 19 3 3 3-3"}]],en=[["path",{d:"M12 12V2"}],["path",{d:"M2 17.002a1 1 0 00.58.91l8.6 3.91a2 2 0 001.65 0l8.58-3.9a1 1 0 00.59-.92"}],["path",{d:"M7.674 8.774 2.58 11.09a1 1 0 000 1.822l8.6 3.91a2 2 0 001.65 0l8.58-3.9a1 1 0 00.59-.92 1 1 0 00-.59-.922l-5.078-2.308"}],["path",{d:"m9 5 3-3 3 3"}]],rn=[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 .83.18 2 2 0 0 0 .83-.18l8.58-3.9a1 1 0 0 0 0-1.832z"}],["path",{d:"M16 17h6"}],["path",{d:"M2.003 11.995a1 1 0 0 0 .597.915l8.58 3.91a2 2 0 0 0 .83.18"}],["path",{d:"M2.003 16.995a1 1 0 0 0 .597.915l8.58 3.91a2 2 0 0 0 .83.18 2 2 0 0 0 .83-.18l2.11-.96"}],["path",{d:"M22.018 12.004a1 1 0 0 1-.598.916l-.177.08"}]],on=[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 .83.18 2 2 0 0 0 .83-.18l8.58-3.9a1 1 0 0 0 0-1.831z"}],["path",{d:"M16 17h6"}],["path",{d:"M19 14v6"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 .825.178"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l2.116-.962"}]],b2=[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"}]],vn=[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1"}],["path",{d:"M18 3v18"}],["path",{d:"m21 18-3 3-3-3"}]],$n=[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"14",y:"3",rx:"1"}],["path",{d:"M3 18h18"}],["path",{d:"m18 21 3-3-3-3"}]],mn=[["rect",{width:"7",height:"9",x:"3",y:"3",rx:"1"}],["rect",{width:"7",height:"5",x:"14",y:"3",rx:"1"}],["rect",{width:"7",height:"9",x:"14",y:"12",rx:"1"}],["rect",{width:"7",height:"5",x:"3",y:"16",rx:"1"}]],yn=[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"14",y:"4",rx:"1"}],["rect",{width:"7",height:"7",x:"4",y:"14",rx:"1"}]],sn=[["circle",{cx:"17.5",cy:"17.5",r:"3.5"}],["circle",{cx:"17.5",cy:"6.5",r:"3.5"}],["circle",{cx:"6.5",cy:"17.5",r:"3.5"}],["circle",{cx:"6.5",cy:"6.5",r:"3.5"}]],Cn=[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1"}],["path",{d:"M14 4h7"}],["path",{d:"M14 9h7"}],["path",{d:"M14 15h7"}],["path",{d:"M14 20h7"}]],un=[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"14",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"14",y:"14",rx:"1"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1"}]],gn=[["rect",{width:"7",height:"18",x:"3",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"14",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"14",y:"14",rx:"1"}]],An=[["rect",{width:"18",height:"7",x:"3",y:"3",rx:"1"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1"}],["rect",{width:"7",height:"7",x:"14",y:"14",rx:"1"}]],Hn=[["rect",{width:"18",height:"7",x:"3",y:"3",rx:"1"}],["rect",{width:"9",height:"7",x:"3",y:"14",rx:"1"}],["rect",{width:"5",height:"7",x:"16",y:"14",rx:"1"}]],Vn=[["path",{d:"M11 20a10 10 0 0010-10 25.9 25.9 0 00-1.04-7.281 1 1 0 00-1.755-.325C15.833 5.5 13 5.5 9.8 6.1A7 7 0 0011 20"}],["path",{d:"M2 21a5 5 0 012.911-4.544C7.613 15.212 8.351 15.24 11 13"}]],Sn=[["path",{d:"M2 22c1.25-.987 2.27-1.975 3.9-2.2a5.56 5.56 0 0 1 3.8 1.5 4 4 0 0 0 6.187-2.353 3.5 3.5 0 0 0 3.69-5.116A3.5 3.5 0 0 0 20.95 8 3.5 3.5 0 1 0 16 3.05a3.5 3.5 0 0 0-5.831 1.373 3.5 3.5 0 0 0-5.116 3.69 4 4 0 0 0-2.348 6.155C3.499 15.42 4.409 16.712 4.2 18.1 3.926 19.743 3.014 20.732 2 22"}],["path",{d:"M2 22 17 7"}]],wn=[["path",{d:"M15 13h4a2 2 0 001.901-1.38l1.057-4.333A1 1 0 0021 6H3a1 1 0 00-.958 1.287L3.1 11.621A2 2 0 005.001 13h4"}],["path",{d:"M15 22V11a1 1 0 00-1-1h-4a1 1 0 00-1 1v11"}],["path",{d:"M18 22H6"}],["path",{d:"M18 6V3a1 1 0 00-1-1h-3"}]],Ln=[["path",{d:"M7 2a1 1 0 0 0-.8 1.6 14 14 0 0 1 0 16.8A1 1 0 0 0 7 22h10a1 1 0 0 0 .8-1.6 14 14 0 0 1 0-16.8A1 1 0 0 0 17 2z"}]],fn=[["path",{d:"M13.433 2a1 1 0 0 1 .824.448 18 18 0 0 1 0 19.104 1 1 0 0 1-.824.448h-2.866a1 1 0 0 1-.824-.448 18 18 0 0 1 0-19.104A1 1 0 0 1 10.567 2z"}]],kn=[["path",{d:"M15.725 7.296h.01"}],["path",{d:"M6 12a6 6 0 016-6"}],["circle",{cx:"12",cy:"12",r:"10"}]],Pn=[["path",{d:"M15 14H9"}],["path",{d:"M21 3v18"}],["path",{d:"M3 3v18"}],["path",{d:"m8 16 3.453-7.648a.6.6 0 011.094.001L16 16"}]],Bn=[["path",{d:"M15 8H9"}],["path",{d:"M21 15.354a4 4 0 100 5.292"}],["path",{d:"M3 18h4a2 2 0 010 4H3.5a.5.5 0 01-.5-.5v-7a.5.5 0 01.5-.5H6a2 2 0 010 4"}],["path",{d:"m8 10 3.453-7.648a.6.6 0 011.094 0L16 10"}]],zn=[["rect",{width:"8",height:"18",x:"3",y:"3",rx:"1"}],["path",{d:"M7 3v18"}],["path",{d:"M20.4 18.9c.2.5-.1 1.1-.6 1.3l-1.9.7c-.5.2-1.1-.1-1.3-.6L11.1 5.1c-.2-.5.1-1.1.6-1.3l1.9-.7c.5-.2 1.1.1 1.3.6Z"}]],Dn=[["path",{d:"m16 6 4 14"}],["path",{d:"M12 6v14"}],["path",{d:"M8 8v12"}],["path",{d:"M4 4v16"}]],Fn=[["path",{d:"M14 12h2v8"}],["path",{d:"M14 20h4"}],["path",{d:"M6 12h4"}],["path",{d:"M6 20h4"}],["path",{d:"M8 20V8a4 4 0 0 1 7.464-2"}]],Rn=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m4.93 4.93 4.24 4.24"}],["path",{d:"m14.83 9.17 4.24-4.24"}],["path",{d:"m14.83 14.83 4.24 4.24"}],["path",{d:"m9.17 14.83-4.24 4.24"}],["circle",{cx:"12",cy:"12",r:"4"}]],bn=[["path",{d:"M16.8 11.2c.8-.9 1.2-2 1.2-3.2a6 6 0 0 0-9.3-5"}],["path",{d:"m2 2 20 20"}],["path",{d:"M6.3 6.3a4.67 4.67 0 0 0 1.2 5.2c.7.7 1.3 1.5 1.5 2.5"}],["path",{d:"M9 18h6"}],["path",{d:"M10 22h4"}]],Tn=[["path",{d:"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"}],["path",{d:"M9 18h6"}],["path",{d:"M10 22h4"}]],qn=[["path",{d:"M12 3V2"}],["path",{d:"M16.066 16.865 7 22l2-11V6a3 3 0 016 0v5l2 11"}],["path",{d:"m19.792 4.5.866-.5"}],["path",{d:"m19.797 13.5.866.5"}],["path",{d:"M21 9h1"}],["path",{d:"M3 9H2"}],["path",{d:"m4.203 13.5-.866.5"}],["path",{d:"M4.208 4.5 3.342 4"}],["path",{d:"M5.5 22h13"}],["path",{d:"m7.932 16.875 7.377-4.178"}],["path",{d:"M8 11h8"}],["path",{d:"M8 7h8"}]],Un=[["path",{d:"M12 3v12"}],["circle",{cx:"12",cy:"18",r:"3"}]],On=[["path",{d:"M9 12h12"}],["circle",{cx:"6",cy:"12",r:"3"}]],Gn=[["path",{d:"M 3 12 L 15 12"}],["circle",{cx:"18",cy:"12",r:"3"}]],Zn=[["path",{d:"M12 9v12"}],["circle",{cx:"12",cy:"6",r:"3"}]],En=[["path",{d:"M15 14H9"}],["path",{d:"M21 21H3"}],["path",{d:"M21 3H3"}],["path",{d:"m8 16 3.453-7.648a.6.6 0 011.094.001L16 16"}]],Wn=[["path",{d:"M7 3.5c5-2 7 2.5 3 4C1.5 10 2 15 5 16c5 2 9-10 14-7s.5 13.5-4 12c-5-2.5.5-11 6-2"}]],In=[["path",{d:"M11 5h2"}],["path",{d:"M15 12h6"}],["path",{d:"M19 5h2"}],["path",{d:"M3 12h6"}],["path",{d:"M3 19h18"}],["path",{d:"M3 5h2"}]],Xn=[["path",{d:"M9 17H7A5 5 0 0 1 7 7"}],["path",{d:"M15 7h2a5 5 0 0 1 4 8"}],["line",{x1:"8",x2:"12",y1:"12",y2:"12"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],Nn=[["path",{d:"M9 17H7A5 5 0 0 1 7 7h2"}],["path",{d:"M15 7h2a5 5 0 1 1 0 10h-2"}],["line",{x1:"8",x2:"16",y1:"12",y2:"12"}]],Kn=[["path",{d:"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"}],["path",{d:"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"}]],Jn=[["path",{d:"M16 5H3"}],["path",{d:"M16 12H3"}],["path",{d:"M11 19H3"}],["path",{d:"m15 18 2 2 4-4"}]],Qn=[["path",{d:"M13 5h8"}],["path",{d:"M13 12h8"}],["path",{d:"M13 19h8"}],["path",{d:"m3 17 2 2 4-4"}],["path",{d:"m3 7 2 2 4-4"}]],jn=[["path",{d:"M3 5h8"}],["path",{d:"M3 12h8"}],["path",{d:"M3 19h8"}],["path",{d:"m15 5 3 3 3-3"}],["path",{d:"m15 19 3-3 3 3"}]],Yn=[["path",{d:"M3 5h8"}],["path",{d:"M3 12h8"}],["path",{d:"M3 19h8"}],["path",{d:"m15 8 3-3 3 3"}],["path",{d:"m15 16 3 3 3-3"}]],_n=[["path",{d:"M16 13v2.2l1.6 1"}],["path",{d:"M3 12h3.458"}],["path",{d:"M3 19h3.832"}],["path",{d:"M3 5h18"}],["circle",{cx:"16",cy:"15",r:"6"}]],xn=[["path",{d:"M10 5h11"}],["path",{d:"M10 12h11"}],["path",{d:"M10 19h11"}],["path",{d:"m3 10 3-3-3-3"}],["path",{d:"m3 20 3-3-3-3"}]],ae=[["path",{d:"M16 5H3"}],["path",{d:"M16 12H3"}],["path",{d:"M9 19H3"}],["path",{d:"m16 16-3 3 3 3"}],["path",{d:"M21 5v12a2 2 0 0 1-2 2h-6"}]],he=[["path",{d:"M12 5H2"}],["path",{d:"M6 12h12"}],["path",{d:"M9 19h6"}],["path",{d:"M16 5h6"}],["path",{d:"M19 8V2"}]],te=[["path",{d:"M2 5h20"}],["path",{d:"M6 12h12"}],["path",{d:"M9 19h6"}]],v=[["path",{d:"M21 5H11"}],["path",{d:"M21 12H11"}],["path",{d:"M21 19H11"}],["path",{d:"m7 8-4 4 4 4"}]],$=[["path",{d:"M21 5H11"}],["path",{d:"M21 12H11"}],["path",{d:"M21 19H11"}],["path",{d:"m3 8 4 4-4 4"}]],de=[["path",{d:"M16 5H3"}],["path",{d:"M11 12H3"}],["path",{d:"M16 19H3"}],["path",{d:"M21 12h-6"}]],ce=[["path",{d:"M16 5H3"}],["path",{d:"M11 12H3"}],["path",{d:"M11 19H3"}],["path",{d:"M21 16V5"}],["circle",{cx:"18",cy:"16",r:"3"}]],Me=[["path",{d:"M11 5h10"}],["path",{d:"M11 12h10"}],["path",{d:"M11 19h10"}],["path",{d:"M4 4h1v5"}],["path",{d:"M4 9h2"}],["path",{d:"M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02"}]],pe=[["path",{d:"M16 5H3"}],["path",{d:"M11 12H3"}],["path",{d:"M16 19H3"}],["path",{d:"M18 9v6"}],["path",{d:"M21 12h-6"}]],le=[["path",{d:"M21 5H3"}],["path",{d:"M7 12H3"}],["path",{d:"M7 19H3"}],["path",{d:"M12 18a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5c-1.33 0-2.54.54-3.41 1.41L11 14"}],["path",{d:"M11 10v4h4"}]],ie=[["path",{d:"M3 19h18"}],["path",{d:"M15 12H3"}],["path",{d:"M9 5H3"}]],ne=[["path",{d:"M13 5h8"}],["path",{d:"M13 12h8"}],["path",{d:"M13 19h8"}],["path",{d:"m3 17 2 2 4-4"}],["rect",{x:"3",y:"4",width:"6",height:"6",rx:"1"}]],ee=[["path",{d:"M15 12H3"}],["path",{d:"M3 5h18"}],["path",{d:"M9 19H3"}]],re=[["path",{d:"M3 5h6"}],["path",{d:"M3 12h13"}],["path",{d:"M3 19h13"}],["path",{d:"m16 8-3-3 3-3"}],["path",{d:"M21 19V7a2 2 0 0 0-2-2h-6"}]],oe=[["path",{d:"M8 5h13"}],["path",{d:"M13 12h8"}],["path",{d:"M13 19h8"}],["path",{d:"M3 10a2 2 0 0 0 2 2h3"}],["path",{d:"M3 5v12a2 2 0 0 0 2 2h3"}]],ve=[["path",{d:"M21 5H3"}],["path",{d:"M10 12H3"}],["path",{d:"M10 19H3"}],["path",{d:"M15 12.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"}]],$e=[["path",{d:"M16 5H3"}],["path",{d:"M11 12H3"}],["path",{d:"M16 19H3"}],["path",{d:"m15.5 9.5 5 5"}],["path",{d:"m20.5 9.5-5 5"}]],me=[["path",{d:"M3 5h.01"}],["path",{d:"M3 12h.01"}],["path",{d:"M3 19h.01"}],["path",{d:"M8 5h13"}],["path",{d:"M8 12h13"}],["path",{d:"M8 19h13"}]],T2=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56"}]],ye=[["path",{d:"M22 12a1 1 0 0 1-10 0 1 1 0 0 0-10 0"}],["path",{d:"M7 20.7a1 1 0 1 1 5-8.7 1 1 0 1 0 5-8.6"}],["path",{d:"M7 3.3a1 1 0 1 1 5 8.6 1 1 0 1 0 5 8.6"}],["circle",{cx:"12",cy:"12",r:"10"}]],se=[["path",{d:"M12 2v4"}],["path",{d:"m16.2 7.8 2.9-2.9"}],["path",{d:"M18 12h4"}],["path",{d:"m16.2 16.2 2.9 2.9"}],["path",{d:"M12 18v4"}],["path",{d:"m4.9 19.1 2.9-2.9"}],["path",{d:"M2 12h4"}],["path",{d:"m4.9 4.9 2.9 2.9"}]],Ce=[["line",{x1:"2",x2:"5",y1:"12",y2:"12"}],["line",{x1:"19",x2:"22",y1:"12",y2:"12"}],["line",{x1:"12",x2:"12",y1:"2",y2:"5"}],["line",{x1:"12",x2:"12",y1:"19",y2:"22"}],["circle",{cx:"12",cy:"12",r:"7"}],["circle",{cx:"12",cy:"12",r:"3"}]],ue=[["line",{x1:"2",x2:"5",y1:"12",y2:"12"}],["line",{x1:"19",x2:"22",y1:"12",y2:"12"}],["line",{x1:"12",x2:"12",y1:"2",y2:"5"}],["line",{x1:"12",x2:"12",y1:"19",y2:"22"}],["circle",{cx:"12",cy:"12",r:"7"}]],ge=[["path",{d:"M12 19v3"}],["path",{d:"M12 2v3"}],["path",{d:"M18.89 13.24a7 7 0 0 0-8.13-8.13"}],["path",{d:"M19 12h3"}],["path",{d:"M2 12h3"}],["path",{d:"m2 2 20 20"}],["path",{d:"M7.05 7.05a7 7 0 0 0 9.9 9.9"}]],q2=[["circle",{cx:"12",cy:"16",r:"1"}],["rect",{width:"18",height:"12",x:"3",y:"10",rx:"2"}],["path",{d:"M7 10V7a5 5 0 0 1 9.33-2.5"}]],Ae=[["circle",{cx:"12",cy:"16",r:"1"}],["rect",{x:"3",y:"10",width:"18",height:"12",rx:"2"}],["path",{d:"M7 10V7a5 5 0 0 1 10 0v3"}]],U2=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2"}],["path",{d:"M7 11V7a5 5 0 0 1 9.9-1"}]],He=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4"}]],Ve=[["path",{d:"m10 17 5-5-5-5"}],["path",{d:"M15 12H3"}],["path",{d:"M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"}]],Se=[["path",{d:"M3 5h1"}],["path",{d:"M3 12h1"}],["path",{d:"M3 19h1"}],["path",{d:"M8 5h1"}],["path",{d:"M8 12h1"}],["path",{d:"M8 19h1"}],["path",{d:"M13 5h8"}],["path",{d:"M13 12h8"}],["path",{d:"M13 19h8"}]],we=[["path",{d:"m16 17 5-5-5-5"}],["path",{d:"M21 12H9"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"}]],Le=[["circle",{cx:"11",cy:"11",r:"8"}],["path",{d:"m21 21-4.3-4.3"}],["path",{d:"M11 11a2 2 0 0 0 4 0 4 4 0 0 0-8 0 6 6 0 0 0 12 0"}]],fe=[["path",{d:"M6 20a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2"}],["path",{d:"M8 18V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v14"}],["path",{d:"M10 20h4"}],["circle",{cx:"16",cy:"20",r:"2"}],["circle",{cx:"8",cy:"20",r:"2"}]],ke=[["path",{d:"m12 15 4 4"}],["path",{d:"M2.352 10.648a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.029-6.029a1 1 0 1 1 3 3l-6.029 6.029a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.365-6.367A1 1 0 0 0 8.716 4.282z"}],["path",{d:"m5 8 4 4"}]],Pe=[["path",{d:"M22 7.7V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8.25"}],["path",{d:"M12 12.996a1.94 1.94 0 0 1-1.03-.296L2 7"}],["path",{d:"m20.69 16.479 1.29 4.88a.5.5 0 0 1-.698.591l-1.843-.849a1 1 0 0 0-.879.001l-1.846.85a.5.5 0 0 1-.692-.593l1.29-4.88"}],["circle",{cx:"19",cy:"14",r:"3"}]],Be=[["path",{d:"M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8"}],["path",{d:"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"}],["path",{d:"m16 19 2 2 4-4"}]],ze=[["path",{d:"M16 14v2.2l1.6 1"}],["path",{d:"m22 7-.759.484"}],["path",{d:"M6.835 20H4a2 2 0 01-2-2V6a2 2 0 012-2h16a2 2 0 012 2v2"}],["path",{d:"M7.605 10.567 2 7"}],["circle",{cx:"16",cy:"16",r:"6"}]],De=[["path",{d:"M12.674 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4.675"}],["path",{d:"m2 7 9 5.701a2 2 0 0 0 2 0l1.084-.686"}],["circle",{cx:"19",cy:"7",r:"3"}]],Fe=[["path",{d:"M22 15V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8"}],["path",{d:"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"}],["path",{d:"M16 19h6"}]],Re=[["path",{d:"M21.2 8.4c.5.38.8.97.8 1.6v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 .8-1.6l8-6a2 2 0 0 1 2.4 0l8 6Z"}],["path",{d:"m22 10-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 10"}]],be=[["path",{d:"M15.363 17.634a2 2 0 00-.506.854l-.837 2.87a.5.5 0 00.62.62l2.87-.837a2 2 0 00.854-.506l3.013-3.009a1 1 0 10-3.004-3.004z"}],["path",{d:"M22 10.38V6a2 2 0 00-2-2H4a2 2 0 00-2 2v12a2 2 0 002 2h6.25"}],["path",{d:"m22 7-8.991 5.727a2 2 0 01-2.009 0L2 7"}]],Te=[["path",{d:"M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8"}],["path",{d:"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"}],["path",{d:"M19 16v6"}],["path",{d:"M16 19h6"}]],O2=[["path",{d:"M22 10.5V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h12.5"}],["path",{d:"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"}],["path",{d:"M18 15.28c.2-.4.5-.8.9-1a2.1 2.1 0 0 1 2.6.4c.3.4.5.8.5 1.3 0 1.3-2 2-2 2"}],["path",{d:"M20 22v.01"}]],qe=[["path",{d:"M22 12.5V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h7.5"}],["path",{d:"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"}],["path",{d:"M18 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"}],["circle",{cx:"18",cy:"18",r:"3"}],["path",{d:"m22 22-1.5-1.5"}]],Ue=[["path",{d:"M22 10.5V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h12.5"}],["path",{d:"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"}],["path",{d:"M20 14v4"}],["path",{d:"M20 22v.01"}]],Oe=[["path",{d:"M22 12.532V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8.792"}],["path",{d:"m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"}],["path",{d:"m16.5 16.5 5 5"}],["path",{d:"m21.5 16.5-5 5"}]],Ge=[["path",{d:"m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"}],["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2"}]],Ze=[["path",{d:"M17 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 1-1.732"}],["path",{d:"m22 5.5-6.419 4.179a2 2 0 0 1-2.162 0L7 5.5"}],["rect",{x:"7",y:"3",width:"15",height:"12",rx:"2"}]],Ee=[["path",{d:"m11 19-1.106-.552a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0l4.212 2.106a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619V14"}],["path",{d:"M15 5.764V14"}],["path",{d:"M21 18h-6"}],["path",{d:"M9 3.236v15"}]],We=[["path",{d:"M19.43 12.935c.357-.967.57-1.955.57-2.935a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32.197 32.197 0 0 0 .813-.728"}],["circle",{cx:"12",cy:"10",r:"3"}],["path",{d:"m16 18 2 2 4-4"}]],Ie=[["path",{d:"M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"}],["polyline",{points:"15,9 18,9 18,11"}],["path",{d:"M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"}],["line",{x1:"6",x2:"7",y1:"10",y2:"10"}]],Xe=[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"}],["path",{d:"m9 10 2 2 4-4"}]],Ne=[["path",{d:"M15 22a1 1 0 0 1-1-1v-4a1 1 0 0 1 .445-.832l3-2a1 1 0 0 1 1.11 0l3 2A1 1 0 0 1 22 17v4a1 1 0 0 1-1 1z"}],["path",{d:"M18 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 .601.2"}],["path",{d:"M18 22v-3"}],["circle",{cx:"10",cy:"10",r:"3"}]],Ke=[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"}],["path",{d:"M9 10h6"}]],Je=[["path",{d:"M18.977 14C19.6 12.701 20 11.343 20 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32 32 0 0 0 .824-.738"}],["circle",{cx:"12",cy:"10",r:"3"}],["path",{d:"M16 18h6"}]],Qe=[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"}],["path",{d:"M12 7v6"}],["path",{d:"M9 10h6"}]],G2=[["path",{d:"M17.97 9.304A8 8 0 0 0 2 10c0 4.69 4.887 9.562 7.022 11.468"}],["path",{d:"M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"}],["circle",{cx:"10",cy:"10",r:"3"}]],je=[["path",{d:"M12.75 7.09a3 3 0 0 1 2.16 2.16"}],["path",{d:"M17.072 17.072c-1.634 2.17-3.527 3.912-4.471 4.727a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 1.432-4.568"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8.475 2.818A8 8 0 0 1 20 10c0 1.183-.31 2.377-.81 3.533"}],["path",{d:"M9.13 9.13a3 3 0 0 0 3.74 3.74"}]],Ye=[["path",{d:"M 12.248 21.969 a 1 1 0 0 1 -0.849 -0.17 C 9.539 20.193 4 14.993 4 10 a 8 8 0 0 1 16 0 C 20 10.42 19.961 10.841 19.888 11.262"}],["path",{d:"m22 22-1.88-1.88"}],["circle",{cx:"12",cy:"10",r:"3"}],["circle",{cx:"18",cy:"18",r:"3"}]],_e=[["path",{d:"M19.914 11.105A7.298 7.298 0 0 0 20 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32 32 0 0 0 .824-.738"}],["circle",{cx:"12",cy:"10",r:"3"}],["path",{d:"M16 18h6"}],["path",{d:"M19 15v6"}]],xe=[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"}],["path",{d:"m14.5 7.5-5 5"}],["path",{d:"m9.5 7.5 5 5"}]],ar=[["path",{d:"M19.752 11.901A7.78 7.78 0 0 0 20 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 19 19 0 0 0 .09-.077"}],["circle",{cx:"12",cy:"10",r:"3"}],["path",{d:"m21.5 15.5-5 5"}],["path",{d:"m21.5 20.5-5-5"}]],hr=[["path",{d:"M18 8c0 3.613-3.869 7.429-5.393 8.795a1 1 0 01-1.214 0C9.87 15.429 6 11.613 6 8a6 6 0 0112 0"}],["path",{d:"M4.474 15h-.197a1 1 0 00-.969.753l-1.097 4.35a1.5 1.5 0 001.444 1.898L20.344 22a1.5 1.5 0 001.446-1.897l-1.098-4.35a1 1 0 00-.969-.753h-.197"}],["circle",{cx:"12",cy:"8",r:"2"}]],tr=[["path",{d:"m11 19-1.106-.552a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0l4.212 2.106a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619V12"}],["path",{d:"M15 5.764V12"}],["path",{d:"M18 15v6"}],["path",{d:"M21 18h-6"}],["path",{d:"M9 3.236v15"}]],dr=[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"}],["circle",{cx:"12",cy:"10",r:"3"}]],cr=[["path",{d:"M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z"}],["path",{d:"M15 5.764v15"}],["path",{d:"M9 3.236v15"}]],Mr=[["path",{d:"m14 6 4 4"}],["path",{d:"M17 3h4v4"}],["path",{d:"m21 3-7.75 7.75"}],["circle",{cx:"9",cy:"15",r:"6"}]],pr=[["path",{d:"M16 3h5v5"}],["path",{d:"m21 3-6.75 6.75"}],["circle",{cx:"10",cy:"14",r:"6"}]],lr=[["path",{d:"M15 3h6v6"}],["path",{d:"m21 3-7 7"}],["path",{d:"m3 21 7-7"}],["path",{d:"M9 21H3v-6"}]],ir=[["path",{d:"M8 3H5a2 2 0 0 0-2 2v3"}],["path",{d:"M21 8V5a2 2 0 0 0-2-2h-3"}],["path",{d:"M3 16v3a2 2 0 0 0 2 2h3"}],["path",{d:"M16 21h3a2 2 0 0 0 2-2v-3"}]],nr=[["path",{d:"M12 12 4.207 4.207A.707.707 0 0 1 4.707 3h14.586a.707.707 0 0 1 .5 1.207z"}],["path",{d:"M12 12v10"}],["path",{d:"M7 22h10"}]],er=[["path",{d:"M7.21 15 2.66 7.14a2 2 0 0 1 .13-2.2L4.4 2.8A2 2 0 0 1 6 2h12a2 2 0 0 1 1.6.8l1.6 2.14a2 2 0 0 1 .14 2.2L16.79 15"}],["path",{d:"M11 12 5.12 2.2"}],["path",{d:"m13 12 5.88-9.8"}],["path",{d:"M8 7h8"}],["circle",{cx:"12",cy:"17",r:"5"}],["path",{d:"M12 18v-2h-.5"}]],rr=[["path",{d:"M11.636 6A13 13 0 0 0 19.4 3.2 1 1 0 0 1 21 4v11.344"}],["path",{d:"M14.378 14.357A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h1"}],["path",{d:"m2 2 20 20"}],["path",{d:"M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14"}],["path",{d:"M8 8v6"}]],or=[["path",{d:"M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z"}],["path",{d:"M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14"}],["path",{d:"M8 6v8"}]],vr=[["path",{d:"M12 12v-2"}],["path",{d:"M12 18v-2"}],["path",{d:"M16 12v-2"}],["path",{d:"M16 18v-2"}],["path",{d:"M2 11h1.5"}],["path",{d:"M20 18v-2"}],["path",{d:"M20.5 11H22"}],["path",{d:"M4 18v-2"}],["path",{d:"M8 12v-2"}],["path",{d:"M8 18v-2"}],["rect",{x:"2",y:"6",width:"20",height:"10",rx:"2"}]],$r=[["path",{d:"M4 5h16"}],["path",{d:"M4 12h16"}],["path",{d:"M4 19h16"}]],mr=[["path",{d:"m8 6 4-4 4 4"}],["path",{d:"M12 2v10.3a4 4 0 0 1-1.172 2.872L4 22"}],["path",{d:"m20 22-5-5"}]],yr=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}],["path",{d:"m16 9-5.5 5.5L8 12"}]],sr=[["path",{d:"m10 9-3 3 3 3"}],["path",{d:"m14 15 3-3-3-3"}],["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}]],Cr=[["path",{d:"M10.1 2.182a10 10 0 013.8 0"}],["path",{d:"M13.9 21.818a10 10 0 01-3.8 0"}],["path",{d:"M17.609 3.72a10 10 0 012.69 2.7"}],["path",{d:"M2.182 13.9a10 10 0 010-3.8"}],["path",{d:"M20.28 17.61a10 10 0 01-2.7 2.69"}],["path",{d:"M21.818 10.1a10 10 0 010 3.8"}],["path",{d:"M3.721 6.391a10 10 0 012.7-2.69"}],["path",{d:"m6.163 21.117-2.906.85a1 1 0 01-1.236-1.169l.965-2.98"}],["path",{d:"m16 9-5.5 5.5L8 12"}]],ur=[["path",{d:"M10.1 2.182a10 10 0 0 1 3.8 0"}],["path",{d:"M13.9 21.818a10 10 0 0 1-3.8 0"}],["path",{d:"M17.609 3.72a10 10 0 0 1 2.69 2.7"}],["path",{d:"M2.182 13.9a10 10 0 0 1 0-3.8"}],["path",{d:"M20.28 17.61a10 10 0 0 1-2.7 2.69"}],["path",{d:"M21.818 10.1a10 10 0 0 1 0 3.8"}],["path",{d:"M3.721 6.391a10 10 0 0 1 2.7-2.69"}],["path",{d:"m6.163 21.117-2.906.85a1 1 0 0 1-1.236-1.169l.965-2.98"}]],gr=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}],["path",{d:"M7.828 13.07A3 3 0 0 1 12 8.764a3 3 0 0 1 5.004 2.224 3 3 0 0 1-.832 2.083l-3.447 3.62a1 1 0 0 1-1.45-.001z"}]],Ar=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}],["path",{d:"M8 12h.01"}],["path",{d:"M12 12h.01"}],["path",{d:"M16 12h.01"}]],Hr=[["path",{d:"m2 2 20 20"}],["path",{d:"M4.93 4.929a10 10 0 0 0-1.938 11.412 2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 0 0 11.302-1.989"}],["path",{d:"M8.35 2.69A10 10 0 0 1 21.3 15.65"}]],Vr=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}],["path",{d:"M8 12h8"}],["path",{d:"M12 8v8"}]],Sr=[["path",{d:"M3.086 17.509a2 2 0 00-.094-1.167 10 10 0 114.777 4.719 2 2 0 00-1.099-.092l-3.413.998a1 1 0 01-1.236-1.168z"}],["path",{d:"M8 12h9"}],["path",{d:"M8 16h5"}],["path",{d:"M8 8h7"}]],wr=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}],["path",{d:"m10 15-3-3 3-3"}],["path",{d:"M7 12h8a2 2 0 0 1 2 2v1"}]],Z2=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"}],["path",{d:"M12 17h.01"}]],Lr=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}],["path",{d:"M12 8v4"}],["path",{d:"M12 16h.01"}]],fr=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}],["path",{d:"m15 9-6 6"}],["path",{d:"m9 9 6 6"}]],kr=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"}]],Pr=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.7.7 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"m9 11 2 2 4-4"}]],Br=[["path",{d:"M14 3h2"}],["path",{d:"M16 19h-2"}],["path",{d:"M2 12v-2"}],["path",{d:"M2 16v5.286a.71.71 0 0 0 1.212.502l1.149-1.149"}],["path",{d:"M20 19a2 2 0 0 0 2-2v-1"}],["path",{d:"M22 10v2"}],["path",{d:"M22 6V5a2 2 0 0 0-2-2"}],["path",{d:"M4 3a2 2 0 0 0-2 2v1"}],["path",{d:"M8 19h2"}],["path",{d:"M8 3h2"}]],zr=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"m10 8-3 3 3 3"}],["path",{d:"m14 14 3-3-3-3"}]],Dr=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"M10 15h4"}],["path",{d:"M10 9h4"}],["path",{d:"M12 7v4"}]],Fr=[["path",{d:"M12.7 3H4a2 2 0 0 0-2 2v16.286a.71.71 0 0 0 1.212.502l2.202-2.202A2 2 0 0 1 6.828 19H20a2 2 0 0 0 2-2v-4.7"}],["circle",{cx:"19",cy:"6",r:"3"}]],Rr=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"M7.5 9.5c0 .687.265 1.383.697 1.844l3.009 3.264a1.14 1.14 0 0 0 .407.314 1 1 0 0 0 .783-.004 1.14 1.14 0 0 0 .398-.31l3.008-3.264A2.77 2.77 0 0 0 16.5 9.5 2.5 2.5 0 0 0 12 8a2.5 2.5 0 0 0-4.5 1.5"}]],br=[["path",{d:"M22 8.5V5a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v16.286a.71.71 0 0 0 1.212.502l2.202-2.202A2 2 0 0 1 6.828 19H10"}],["path",{d:"M20 15v-2a2 2 0 0 0-4 0v2"}],["rect",{x:"14",y:"15",width:"8",height:"5",rx:"1"}]],Tr=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"M12 11h.01"}],["path",{d:"M16 11h.01"}],["path",{d:"M8 11h.01"}]],qr=[["path",{d:"M19 19H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.7.7 0 0 1 2 21.286V5a2 2 0 0 1 1.184-1.826"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8.656 3H20a2 2 0 0 1 2 2v11.344"}]],Ur=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"M12 8v6"}],["path",{d:"M9 11h6"}]],Or=[["path",{d:"M12 3H4a2 2 0 0 0-2 2v16.286a.71.71 0 0 0 1.212.502l2.202-2.202A2 2 0 0 1 6.828 19H20a2 2 0 0 0 2-2v-4"}],["path",{d:"M16 3h6v6"}],["path",{d:"m16 9 6-6"}]],Gr=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"m10 8-3 3 3 3"}],["path",{d:"M17 14v-1a2 2 0 0 0-2-2H7"}]],Zr=[["path",{d:"M14 14a2 2 0 0 0 2-2V8h-2"}],["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"M8 14a2 2 0 0 0 2-2V8H8"}]],Er=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"M7 11h10"}],["path",{d:"M7 15h6"}],["path",{d:"M7 7h8"}]],Wr=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"M12 15h.01"}],["path",{d:"M12 7v4"}]],Ir=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{d:"m14.5 8.5-5 5"}],["path",{d:"m9.5 8.5 5 5"}]],Xr=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}]],Nr=[["path",{d:"M19.95 10.05a7 7 0 011.412 7.872 1 1 0 00-.058.787l.675 2.089a1 1 0 01-1.236 1.168l-2.155-.631a1 1 0 00-.745.06 7 7 0 01-7.793-1.445"}],["path",{d:"M2.696 12.708a1 1 0 00-.058-.785 7 7 0 113.518 3.473 1 1 0 00-.744-.061l-2.155.63a1 1 0 01-1.236-1.167z"}]],Kr=[["path",{d:"M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"}],["path",{d:"M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1"}]],Jr=[["path",{d:"M12 11.4V9.1"}],["path",{d:"m12 17 6.59-6.59"}],["path",{d:"m15.05 5.7-.218-.691a3 3 0 0 0-5.663 0L4.418 19.695A1 1 0 0 0 5.37 21h13.253a1 1 0 0 0 .951-1.31L18.45 16.2"}],["circle",{cx:"20",cy:"9",r:"2"}]],Qr=[["path",{d:"M10 3v2.341"}],["path",{d:"M12 17v4"}],["path",{d:"M14 5v.341"}],["path",{d:"M18 5v13"}],["path",{d:"M2 10v3"}],["path",{d:"M22 10v3"}],["path",{d:"M6 6v11"}],["path",{d:"M9 21h6"}],["rect",{width:"4",height:"8",x:"10",y:"9",rx:"2"}]],jr=[["path",{d:"M12 19v3"}],["path",{d:"M15 9.34V5a3 3 0 0 0-5.68-1.33"}],["path",{d:"M16.95 16.95A7 7 0 0 1 5 12v-2"}],["path",{d:"M18.89 13.23A7 7 0 0 0 19 12v-2"}],["path",{d:"m2 2 20 20"}],["path",{d:"M9 9v3a3 3 0 0 0 5.12 2.12"}]],E2=[["path",{d:"M12 17v4"}],["path",{d:"M18 11a6 6 0 00-3-5.197"}],["path",{d:"M2 11a10 10 0 015-8.662"}],["path",{d:"M22 11a10 10 0 00-5-8.662"}],["path",{d:"M6 11a6 6 0 013-5.197"}],["path",{d:"M9 21h6"}],["rect",{x:"10",y:"9",width:"4",height:"8",rx:"2"}]],Yr=[["path",{d:"M12 19v3"}],["path",{d:"M19 10v2a7 7 0 0 1-14 0v-2"}],["rect",{x:"9",y:"2",width:"6",height:"13",rx:"3"}]],W2=[["path",{d:"m11 7.601-5.994 8.19a1 1 0 0 0 .1 1.298l.817.818a1 1 0 0 0 1.314.087L15.09 12"}],["path",{d:"M16.5 21.174C15.5 20.5 14.372 20 13 20c-2.058 0-3.928 2.356-6 2-2.072-.356-2.775-3.369-1.5-4.5"}],["circle",{cx:"16",cy:"7",r:"5"}]],_r=[["path",{d:"M10 12h4"}],["path",{d:"M10 17h4"}],["path",{d:"M10 7h4"}],["path",{d:"M18 12h2"}],["path",{d:"M18 18h2"}],["path",{d:"M18 6h2"}],["path",{d:"M4 12h2"}],["path",{d:"M4 18h2"}],["path",{d:"M4 6h2"}],["rect",{x:"6",y:"2",width:"12",height:"20",rx:"2"}]],xr=[["path",{d:"M6 18h8"}],["path",{d:"M3 22h18"}],["path",{d:"M14 22a7 7 0 1 0 0-14h-1"}],["path",{d:"M9 14h2"}],["path",{d:"M9 12a2 2 0 0 1-2-2V6h6v4a2 2 0 0 1-2 2Z"}],["path",{d:"M12 6V3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3"}]],ao=[["rect",{width:"20",height:"15",x:"2",y:"4",rx:"2"}],["rect",{width:"8",height:"7",x:"6",y:"8",rx:"1"}],["path",{d:"M18 8v7"}],["path",{d:"M6 19v2"}],["path",{d:"M18 19v2"}]],ho=[["path",{d:"M12 18h.01"}],["path",{d:"M15 2.458V5a1 1 0 01-1 1h-4a1 1 0 01-1-1V2.458"}],["path",{d:"M16 16h.01"}],["path",{d:"M18 12h.01"}],["path",{d:"M6 12h.01"}],["path",{d:"M8 16h.01"}],["circle",{cx:"12",cy:"12",r:"10"}]],to=[["path",{d:"M12 13v8"}],["path",{d:"M12 3v3"}],["path",{d:"M18.172 6a2 2 0 0 1 1.414.586l2.06 2.06a1.207 1.207 0 0 1 0 1.708l-2.06 2.06a2 2 0 0 1-1.414.586H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"}]],co=[["path",{d:"M8 2h8"}],["path",{d:"M9 2v1.343M15 2v2.789a4 4 0 0 0 .672 2.219l.656.984a4 4 0 0 1 .672 2.22v1.131M7.8 7.8l-.128.192A4 4 0 0 0 7 10.212V20a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-3"}],["path",{d:"M7 15a6.47 6.47 0 0 1 5 0 6.472 6.472 0 0 0 3.435.435"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],Mo=[["path",{d:"m14 10 7-7"}],["path",{d:"M20 10h-6V4"}],["path",{d:"m3 21 7-7"}],["path",{d:"M4 14h6v6"}]],po=[["path",{d:"M8 2h8"}],["path",{d:"M9 2v2.789a4 4 0 0 1-.672 2.219l-.656.984A4 4 0 0 0 7 10.212V20a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-9.789a4 4 0 0 0-.672-2.219l-.656-.984A4 4 0 0 1 15 4.788V2"}],["path",{d:"M7 15a6.472 6.472 0 0 1 5 0 6.47 6.47 0 0 0 5 0"}]],lo=[["path",{d:"M8 3v3a2 2 0 0 1-2 2H3"}],["path",{d:"M21 8h-3a2 2 0 0 1-2-2V3"}],["path",{d:"M3 16h3a2 2 0 0 1 2 2v3"}],["path",{d:"M16 21v-3a2 2 0 0 1 2-2h3"}]],io=[["path",{d:"M5 12h14"}]],no=[["path",{d:"M11 6 8 9"}],["path",{d:"m16 7-8 8"}],["rect",{x:"4",y:"2",width:"16",height:"20",rx:"2"}]],eo=[["path",{d:"M10 6.6 8.6 8"}],["path",{d:"M12 18v4"}],["path",{d:"M15 7.5 9.5 13"}],["path",{d:"M7 22h10"}],["circle",{cx:"12",cy:"10",r:"8"}]],ro=[["path",{d:"M11 13a3 3 0 1 1 2.83-4H14a2 2 0 0 1 0 4z"}],["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}],["rect",{x:"2",y:"3",width:"20",height:"14",rx:"2"}]],oo=[["path",{d:"m9 10 2 2 4-4"}],["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2"}],["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}]],vo=[["path",{d:"M12 17v4"}],["path",{d:"m14.305 7.53.923-.382"}],["path",{d:"m15.228 4.852-.923-.383"}],["path",{d:"m16.852 3.228-.383-.924"}],["path",{d:"m16.852 8.772-.383.923"}],["path",{d:"m19.148 3.228.383-.924"}],["path",{d:"m19.53 9.696-.382-.924"}],["path",{d:"m20.772 4.852.924-.383"}],["path",{d:"m20.772 7.148.924.383"}],["path",{d:"M22 13v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"}],["path",{d:"M8 21h8"}],["circle",{cx:"18",cy:"6",r:"3"}]],$o=[["path",{d:"M12 17v4"}],["path",{d:"M22 12.307V15a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8.693"}],["path",{d:"M8 21h8"}],["circle",{cx:"19",cy:"6",r:"3"}]],mo=[["path",{d:"M12 13V7"}],["path",{d:"m15 10-3 3-3-3"}],["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2"}],["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}]],yo=[["path",{d:"M12 17v4"}],["path",{d:"M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8 21h8"}],["path",{d:"M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042"}]],so=[["path",{d:"M10 13V7"}],["path",{d:"M14 13V7"}],["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2"}],["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}]],Co=[["path",{d:"M10 15H4a2 2 0 01-2-2V7a2 2 0 012-2h6"}],["path",{d:"M10 19H5"}],["path",{d:"M14 11h8"}],["path",{d:"M14 7h8"}],["path",{d:"M18 17h.01"}],["path",{d:"M9 19v-4"}],["rect",{x:"14",y:"3",width:"8",height:"18",rx:"1"}]],uo=[["path",{d:"M15.033 9.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56V7.648a.645.645 0 0 1 .967-.56z"}],["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}],["rect",{x:"2",y:"3",width:"20",height:"14",rx:"2"}]],go=[["path",{d:"M18 8V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h8"}],["path",{d:"M10 19v-3.96 3.15"}],["path",{d:"M7 19h5"}],["rect",{width:"6",height:"10",x:"16",y:"12",rx:"2"}]],Ao=[["path",{d:"M5.5 20H8"}],["path",{d:"M17 9h.01"}],["rect",{width:"10",height:"16",x:"12",y:"4",rx:"2"}],["path",{d:"M8 6H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h4"}],["circle",{cx:"17",cy:"15",r:"1"}]],Ho=[["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}],["rect",{x:"2",y:"3",width:"20",height:"14",rx:"2"}],["rect",{x:"9",y:"7",width:"6",height:"6",rx:"1"}]],Vo=[["path",{d:"m9 10 3-3 3 3"}],["path",{d:"M12 13V7"}],["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2"}],["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}]],So=[["path",{d:"m14.5 12.5-5-5"}],["path",{d:"m9.5 12.5 5-5"}],["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2"}],["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}]],wo=[["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2"}],["line",{x1:"8",x2:"16",y1:"21",y2:"21"}],["line",{x1:"12",x2:"12",y1:"17",y2:"21"}]],Lo=[["path",{d:"M18 5h4"}],["path",{d:"M20 3v4"}],["path",{d:"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"}]],fo=[["path",{d:"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"}]],ko=[["path",{d:"M10 22a3 3 0 01-3-3"}],["path",{d:"M10 22c2.761 0 5-1.79 5-4-4.42 0-4.08-5-8.5-5a4.501 4.501 0 000 9z"}],["path",{d:"M10 3H8"}],["path",{d:"M12.5 11.5 22 2"}],["path",{d:"M20 13v4"}],["path",{d:"M22 15h-4"}],["path",{d:"M4 5v4"}],["path",{d:"M6 7H2"}],["path",{d:"m6.98 13.02 2.665-2.664a1.21 1.21 0 011.71 0l2.29 2.288a1.21 1.21 0 010 1.712l-2.088 2.087"}],["path",{d:"M9 2v2"}]],Po=[["path",{d:"M10 22c2.761 0 5-1.79 5-4-4.42 0-4.08-5-8.5-5a1 1 0 100 9za3 3 0 01-3-3"}],["path",{d:"M12.5 11.5 22 2"}],["path",{d:"m6.98 13.02 2.665-2.664a1.21 1.21 0 011.71 0l2.29 2.288a1.21 1.21 0 010 1.712l-2.088 2.087"}]],Bo=[["path",{d:"M12.268 2a2 2 0 003.465 2"}],["path",{d:"M14 5 L14 8"}],["path",{d:"M16 22v-3a2 2 0 00-4 0v3"}],["path",{d:"M21 13c-.662-1.497-1.666-2.753-2.9-3.63C16.825 8.47 15.422 8 14 8s-2.826.47-4.1 1.37C8.668 10.248 7.663 11.504 7 13z"}],["path",{d:"M3 9h4"}],["path",{d:"M7 22V6a5 5 0 00-2-4 5 5 0 00-2 4v14a2 2 0 002 2h14a2 2 0 002-2v-7"}]],zo=[["path",{d:"m18 14-1-3"}],["path",{d:"m3 9 6 2a2 2 0 0 1 2-2h2a2 2 0 0 1 1.99 1.81"}],["path",{d:"M8 17h3a1 1 0 0 0 1-1 6 6 0 0 1 6-6 1 1 0 0 0 1-1v-.75A5 5 0 0 0 17 5"}],["circle",{cx:"19",cy:"17",r:"3"}],["circle",{cx:"5",cy:"17",r:"3"}]],Do=[["path",{d:"m8 3 4 8 5-5 5 15H2L8 3z"}],["path",{d:"M4.14 15.08c2.62-1.57 5.24-1.43 7.86.42 2.74 1.94 5.49 2 8.23.19"}]],Fo=[["path",{d:"m8 3 4 8 5-5 5 15H2L8 3z"}]],Ro=[["path",{d:"M12 7.318V10"}],["path",{d:"M5 10v5a7 7 0 0 0 14 0V9c0-3.527-2.608-6.515-6-7"}],["circle",{cx:"7",cy:"4",r:"2"}]],bo=[["path",{d:"M12 6v.343"}],["path",{d:"M18.218 18.218A7 7 0 0 1 5 15V9a7 7 0 0 1 .782-3.218"}],["path",{d:"M19 13.343V9A7 7 0 0 0 8.56 2.902"}],["path",{d:"M22 22 2 2"}]],To=[["path",{d:"m15.55 8.45 5.138 2.087a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063L8.45 15.551"}],["path",{d:"M22 2 2 22"}],["path",{d:"m6.816 11.528-2.779-6.84a.495.495 0 0 1 .651-.651l6.84 2.779"}]],qo=[["path",{d:"M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z"}]],Uo=[["path",{d:"M2.034 2.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.944L8.204 7.545a1 1 0 0 0-.66.66l-1.066 3.443a.5.5 0 0 1-.944.033z"}],["circle",{cx:"16",cy:"16",r:"6"}],["path",{d:"m11.8 11.8 8.4 8.4"}]],Oo=[["path",{d:"M14 4.1 12 6"}],["path",{d:"m5.1 8-2.9-.8"}],["path",{d:"m6 12-1.9 2"}],["path",{d:"M7.2 2.2 8 5.1"}],["path",{d:"M9.037 9.69a.498.498 0 0 1 .653-.653l11 4.5a.5.5 0 0 1-.074.949l-4.349 1.041a1 1 0 0 0-.74.739l-1.04 4.35a.5.5 0 0 1-.95.074z"}]],Go=[["path",{d:"M12.586 12.586 19 19"}],["path",{d:"M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"}]],Zo=[["path",{d:"M12 7.318V10"}],["path",{d:"M19 10v5a7 7 0 0 1-14 0V9c0-3.527 2.608-6.515 6-7"}],["circle",{cx:"17",cy:"4",r:"2"}]],Eo=[["rect",{x:"5",y:"2",width:"14",height:"20",rx:"7"}],["path",{d:"M12 6v4"}]],Wo=[["path",{d:"M13.074 7.417a2.6 2.6 0 012.989.099c1.829 1.36 3.053 2.076 5.479 3.644a1 1 0 01.308 1.368 11.6 11.6 0 01-1.617 2.05"}],["path",{d:"M2 12a50.5 50.5 0 0010.99.99"}],["path",{d:"m2 2 20 20"}],["path",{d:"M21 11a1 1 0 011 1 51 51 0 01-3.734.61"}],["path",{d:"M7.695 7.695c-1.7 1.247-2.92 1.967-5.238 3.464a1 1 0 00-.307 1.369 11.6 11.6 0 0014.766 4.388"}]],Io=[["path",{d:"M2 12a50.5 50.5 0 0020 0 1 1 0 00-1-1"}],["path",{d:"M2.457 11.159a1 1 0 00-.307 1.369 11.59 11.59 0 0019.7 0 1 1 0 00-.308-1.368c-2.426-1.568-3.65-2.284-5.479-3.644a2.6 2.6 0 00-3.373.208 1 1 0 01-1.38 0 2.62 2.62 0 00-3.373-.208c-1.83 1.36-3.053 2.076-5.48 3.643"}]],Xo=[["path",{d:"M19 13v6h-6"}],["path",{d:"M5 11V5h6"}],["path",{d:"m5 5 14 14"}]],I2=[["path",{d:"M5 3v16h16"}],["path",{d:"m5 19 6-6"}],["path",{d:"m2 6 3-3 3 3"}],["path",{d:"m18 16 3 3-3 3"}]],No=[["path",{d:"M11 19H5v-6"}],["path",{d:"M13 5h6v6"}],["path",{d:"M19 5 5 19"}]],Ko=[["path",{d:"M11 19H5V13"}],["path",{d:"M19 5L5 19"}]],Jo=[["path",{d:"M19 13V19H13"}],["path",{d:"M5 5L19 19"}]],Qo=[["path",{d:"M8 18L12 22L16 18"}],["path",{d:"M12 2V22"}]],jo=[["path",{d:"m18 8 4 4-4 4"}],["path",{d:"M2 12h20"}],["path",{d:"m6 8-4 4 4 4"}]],Yo=[["path",{d:"M6 8L2 12L6 16"}],["path",{d:"M2 12H22"}]],_o=[["path",{d:"M18 8L22 12L18 16"}],["path",{d:"M2 12H22"}]],xo=[["path",{d:"M5 11V5H11"}],["path",{d:"M5 5L19 19"}]],av=[["path",{d:"M13 5H19V11"}],["path",{d:"M19 5L5 19"}]],hv=[["path",{d:"M8 6L12 2L16 6"}],["path",{d:"M12 2V22"}]],tv=[["path",{d:"M12 2v20"}],["path",{d:"m15 19-3 3-3-3"}],["path",{d:"m19 9 3 3-3 3"}],["path",{d:"M2 12h20"}],["path",{d:"m5 9-3 3 3 3"}],["path",{d:"m9 5 3-3 3 3"}]],dv=[["path",{d:"M12 2v20"}],["path",{d:"m8 18 4 4 4-4"}],["path",{d:"m8 6 4-4 4 4"}]],cv=[["path",{d:"M9.31 9.31 5 21l7-4 7 4-1.17-3.17"}],["path",{d:"M14.53 8.88 12 2l-1.17 3.17"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],Mv=[["polygon",{points:"12 2 19 21 12 17 5 21 12 2"}]],pv=[["path",{d:"M8.43 8.43 3 11l8 2 2 8 2.57-5.43"}],["path",{d:"M17.39 11.73 22 2l-9.73 4.61"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],lv=[["polygon",{points:"3 11 22 2 13 21 11 13 3 11"}]],iv=[["path",{d:"M18 16.173 A4.74 4.74 0 0 0 13.496 8.005"}],["path",{d:"M4 3 L20 3"}],["path",{d:"M5 13 L13.5 21"}],["path",{d:"M5 13 L9 13"}],["path",{d:"M8 13 C15.5 13 14.667 3 8 3"}]],nv=[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3"}],["path",{d:"M12 12V8"}]],ev=[["path",{d:"M15 18h-5"}],["path",{d:"M18 14h-8"}],["path",{d:"M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2"}],["rect",{width:"8",height:"4",x:"10",y:"6",rx:"1"}]],rv=[["path",{d:"M6 8.32a7.43 7.43 0 0 1 0 7.36"}],["path",{d:"M9.46 6.21a11.76 11.76 0 0 1 0 11.58"}],["path",{d:"M12.91 4.1a15.91 15.91 0 0 1 .01 15.8"}],["path",{d:"M16.37 2a20.16 20.16 0 0 1 0 20"}]],ov=[["path",{d:"M12 2v10"}],["path",{d:"m8.5 4 7 4"}],["path",{d:"m8.5 8 7-4"}],["circle",{cx:"12",cy:"17",r:"5"}]],vv=[["path",{d:"M16 11.75V22"}],["path",{d:"M2 10h4"}],["path",{d:"M2 14h4"}],["path",{d:"M2 18h4"}],["path",{d:"M2 6h4"}],["path",{d:"M20 11.75V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h5.65"}],["circle",{cx:"18",cy:"5",r:"3"}]],$v=[["path",{d:"M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4"}],["path",{d:"M2 6h4"}],["path",{d:"M2 10h4"}],["path",{d:"M2 14h4"}],["path",{d:"M2 18h4"}],["path",{d:"M21.378 5.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"}]],mv=[["path",{d:"M2 6h4"}],["path",{d:"M2 10h4"}],["path",{d:"M2 14h4"}],["path",{d:"M2 18h4"}],["rect",{width:"16",height:"20",x:"4",y:"2",rx:"2"}],["path",{d:"M15 2v20"}],["path",{d:"M15 7h5"}],["path",{d:"M15 12h5"}],["path",{d:"M15 17h5"}]],yv=[["path",{d:"M2 6h4"}],["path",{d:"M2 10h4"}],["path",{d:"M2 14h4"}],["path",{d:"M2 18h4"}],["rect",{width:"16",height:"20",x:"4",y:"2",rx:"2"}],["path",{d:"M9.5 8h5"}],["path",{d:"M9.5 12H16"}],["path",{d:"M9.5 16H14"}]],sv=[["path",{d:"M2 6h4"}],["path",{d:"M2 10h4"}],["path",{d:"M2 14h4"}],["path",{d:"M2 18h4"}],["rect",{width:"16",height:"20",x:"4",y:"2",rx:"2"}],["path",{d:"M16 2v20"}]],Cv=[["path",{d:"M8 2v4"}],["path",{d:"M12 2v4"}],["path",{d:"M16 2v4"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v2"}],["path",{d:"M20 12v2"}],["path",{d:"M20 18v2a2 2 0 0 1-2 2h-1"}],["path",{d:"M13 22h-2"}],["path",{d:"M7 22H6a2 2 0 0 1-2-2v-2"}],["path",{d:"M4 14v-2"}],["path",{d:"M4 8V6a2 2 0 0 1 2-2h2"}],["path",{d:"M8 10h6"}],["path",{d:"M8 14h8"}],["path",{d:"M8 18h5"}]],uv=[["path",{d:"M8 2v4"}],["path",{d:"M12 2v4"}],["path",{d:"M16 2v4"}],["rect",{width:"16",height:"18",x:"4",y:"4",rx:"2"}],["path",{d:"M8 10h6"}],["path",{d:"M8 14h8"}],["path",{d:"M8 18h5"}]],gv=[["path",{d:"M11.868 11.868a.88.88 0 01-.488.252c-1.78.28-3.54-.17-4.88-.62 0 1.272-.229 3.578-.653 5.347a10 10 0 01-.417 1.363c-.21.52-.82.55-1.17.12a10 10 0 01.677-13.393"}],["path",{d:"M12.14 6.485a27.4 27.4 0 004.707-.638L20 9a7.23 7.23 0 011.706 7.05"}],["path",{d:"m2 2 20 20"}],["path",{d:"M20.707 20.707A1 1 0 0120 21h-1c-1.069 0-1.648.242-2.485.552A7.2 7.2 0 019.002 20l-3.155-3.153"}],["path",{d:"M8.356 2.7a10 10 0 019.974 1.56c.43.35.4.97-.12 1.17a10 10 0 01-1.363.417"}]],Av=[["path",{d:"M16.847 5.847 20 9a7.23 7.23 0 011.551 7.516C21.241 17.352 21 17.932 21 19v1a1 1 0 01-1 1h-1c-1.069 0-1.648.242-2.485.552A7.2 7.2 0 019.002 20l-3.155-3.153"}],["path",{d:"M18.21 5.43c-1.71.69-5.07 1.07-6.71 1.07.46 1.38.91 2.74.61 4.88a.88.88 0 01-.73.74c-1.78.28-3.54-.17-4.88-.62 0 1.64-.38 5-1.07 6.71-.21.52-.82.55-1.17.12A10 10 0 0118.33 4.26c.43.35.4.97-.12 1.17"}],["path",{d:"M4.93 4.93 3 3a.7.7 0 010-1"}]],X2=[["path",{d:"M12 16h.01"}],["path",{d:"M12 8v4"}],["path",{d:"M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z"}]],Hv=[["path",{d:"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z"}],["path",{d:"M8 12h8"}]],N2=[["path",{d:"M10 15V9"}],["path",{d:"M14 15V9"}],["path",{d:"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z"}]],K2=[["path",{d:"m15 9-6 6"}],["path",{d:"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z"}],["path",{d:"m9 9 6 6"}]],Vv=[["path",{d:"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z"}]],Sv=[["path",{d:"M3 20h4.5a.5.5 0 0 0 .5-.5v-.282a.52.52 0 0 0-.247-.437 8 8 0 1 1 8.494-.001.52.52 0 0 0-.247.438v.282a.5.5 0 0 0 .5.5H21"}]],wv=[["path",{d:"M14 3h7"}],["path",{d:"M3 3h5.28a1 1 0 0 1 .948.684l5.544 16.632a1 1 0 0 0 .949.684H21"}]],Lv=[["path",{d:"M20.341 6.484A10 10 0 0 1 10.266 21.85"}],["path",{d:"M3.659 17.516A10 10 0 0 1 13.74 2.152"}],["circle",{cx:"12",cy:"12",r:"3"}],["circle",{cx:"19",cy:"5",r:"2"}],["circle",{cx:"5",cy:"19",r:"2"}]],fv=[["path",{d:"M12 12V4a1 1 0 0 1 1-1h6.297a1 1 0 0 1 .651 1.759l-4.696 4.025"}],["path",{d:"m12 21-7.414-7.414A2 2 0 0 1 4 12.172V6.415a1.002 1.002 0 0 1 1.707-.707L20 20.009"}],["path",{d:"m12.214 3.381 8.414 14.966a1 1 0 0 1-.167 1.199l-1.168 1.163a1 1 0 0 1-.706.291H6.351a1 1 0 0 1-.625-.219L3.25 18.8a1 1 0 0 1 .631-1.781l4.165.027"}]],kv=[["path",{d:"M12 3v6"}],["path",{d:"M16.76 3a2 2 0 0 1 1.8 1.1l2.23 4.479a2 2 0 0 1 .21.891V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9.472a2 2 0 0 1 .211-.894L5.45 4.1A2 2 0 0 1 7.24 3z"}],["path",{d:"M3.054 9.013h17.893"}]],Pv=[["path",{d:"M12 22V12"}],["path",{d:"M16 17h6"}],["path",{d:"M21 13V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.729l7 4a2 2 0 0 0 2 .001l1.675-.955"}],["path",{d:"M3.29 7 12 12l8.71-5"}],["path",{d:"m7.5 4.27 8.997 5.148"}]],Bv=[["path",{d:"M12 22V12"}],["path",{d:"m16 17 2 2 4-4"}],["path",{d:"M21 11.127V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.729l7 4a2 2 0 0 0 2 .001l1.32-.753"}],["path",{d:"M3.29 7 12 12l8.71-5"}],["path",{d:"m7.5 4.27 8.997 5.148"}]],zv=[["path",{d:"M12 22v-9"}],["path",{d:"M15.17 2.21a1.67 1.67 0 0 1 1.63 0L21 4.57a1.93 1.93 0 0 1 0 3.36L8.82 14.79a1.655 1.655 0 0 1-1.64 0L3 12.43a1.93 1.93 0 0 1 0-3.36z"}],["path",{d:"M20 13v3.87a2.06 2.06 0 0 1-1.11 1.83l-6 3.08a1.93 1.93 0 0 1-1.78 0l-6-3.08A2.06 2.06 0 0 1 4 16.87V13"}],["path",{d:"M21 12.43a1.93 1.93 0 0 0 0-3.36L8.83 2.2a1.64 1.64 0 0 0-1.63 0L3 4.57a1.93 1.93 0 0 0 0 3.36l12.18 6.86a1.636 1.636 0 0 0 1.63 0z"}]],Dv=[["path",{d:"M12 22V12"}],["path",{d:"M16 17h6"}],["path",{d:"M19 14v6"}],["path",{d:"M21 10.535V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.729l7 4a2 2 0 0 0 2 .001l1.675-.955"}],["path",{d:"M3.29 7 12 12l8.71-5"}],["path",{d:"m7.5 4.27 8.997 5.148"}]],Fv=[["path",{d:"M12 22V12"}],["path",{d:"M20.27 18.27 22 20"}],["path",{d:"M21 10.498V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.729l7 4a2 2 0 0 0 2 .001l.98-.559"}],["path",{d:"M3.29 7 12 12l8.71-5"}],["path",{d:"m7.5 4.27 8.997 5.148"}],["circle",{cx:"18.5",cy:"16.5",r:"2.5"}]],Rv=[["path",{d:"M12 22V12"}],["path",{d:"m16.5 14.5 5 5"}],["path",{d:"m16.5 19.5 5-5"}],["path",{d:"M21 10.5V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.729l7 4a2 2 0 0 0 2 .001l.13-.074"}],["path",{d:"M3.29 7 12 12l8.71-5"}],["path",{d:"m7.5 4.27 8.997 5.148"}]],bv=[["path",{d:"M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"}],["path",{d:"M12 22V12"}],["polyline",{points:"3.29 7 12 12 20.71 7"}],["path",{d:"m7.5 4.27 9 5.15"}]],Tv=[["path",{d:"M11 7 6 2"}],["path",{d:"M18.992 12H2.041"}],["path",{d:"M21.145 18.38A3.34 3.34 0 0 1 20 16.5a3.3 3.3 0 0 1-1.145 1.88c-.575.46-.855 1.02-.855 1.595A2 2 0 0 0 20 22a2 2 0 0 0 2-2.025c0-.58-.285-1.13-.855-1.595"}],["path",{d:"m8.5 4.5 2.148-2.148a1.205 1.205 0 0 1 1.704 0l7.296 7.296a1.205 1.205 0 0 1 0 1.704l-7.592 7.592a3.615 3.615 0 0 1-5.112 0l-3.888-3.888a3.615 3.615 0 0 1 0-5.112L5.67 7.33"}]],qv=[["rect",{width:"16",height:"6",x:"2",y:"2",rx:"2"}],["path",{d:"M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"}],["rect",{width:"4",height:"6",x:"8",y:"16",rx:"1"}]],Uv=[["path",{d:"m14.622 17.897-10.68-2.913"}],["path",{d:"M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z"}],["path",{d:"M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15"}]],J2=[["path",{d:"M10 2v2"}],["path",{d:"M14 2v4"}],["path",{d:"M17 2a1 1 0 0 1 1 1v9H6V3a1 1 0 0 1 1-1z"}],["path",{d:"M6 12a1 1 0 0 0-1 1v1a2 2 0 0 0 2 2h2a1 1 0 0 1 1 1v2.9a2 2 0 1 0 4 0V17a1 1 0 0 1 1-1h2a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1"}]],Ov=[["path",{d:"M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z"}],["circle",{cx:"13.5",cy:"6.5",r:".5",fill:"currentColor"}],["circle",{cx:"17.5",cy:"10.5",r:".5",fill:"currentColor"}],["circle",{cx:"6.5",cy:"12.5",r:".5",fill:"currentColor"}],["circle",{cx:"8.5",cy:"7.5",r:".5",fill:"currentColor"}]],Gv=[["path",{d:"M11.25 17.25h1.5L12 18z"}],["path",{d:"m15 12 2 2"}],["path",{d:"M17.902 6.599a8 8 0 0 0-.5-.5"}],["path",{d:"M2 14.5C2 19.47 6.48 22 12 22s10-2.53 10-7.5a10 10 0 0 0-1.3-4.83 4.5 4.5 0 1 0-7.05-5.5 8 8 0 0 0-3.3 0 4.5 4.5 0 1 0-7.04 5.5A10 10 0 0 0 2 14.5"}],["path",{d:"M6.099 6.599a8 8 0 0 1 .5-.5"}],["path",{d:"m9 12-2 2"}]],Zv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 15h18"}],["path",{d:"m15 8-3 3-3-3"}]],Q2=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M14 15h1"}],["path",{d:"M19 15h2"}],["path",{d:"M3 15h2"}],["path",{d:"M9 15h1"}]],Ev=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 15h18"}],["path",{d:"m9 10 3-3 3 3"}]],Wv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 15h18"}]],j2=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M9 3v18"}],["path",{d:"m16 15-3-3 3-3"}]],Y2=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M9 14v1"}],["path",{d:"M9 19v2"}],["path",{d:"M9 3v2"}],["path",{d:"M9 9v1"}]],_2=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M9 3v18"}],["path",{d:"m14 9 3 3-3 3"}]],Iv=[["path",{d:"M15 10V9"}],["path",{d:"M15 15v-1"}],["path",{d:"M15 21v-2"}],["path",{d:"M15 5V3"}],["path",{d:"M9 10V9"}],["path",{d:"M9 15v-1"}],["path",{d:"M9 21v-2"}],["path",{d:"M9 5V3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],x2=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M9 3v18"}]],Xv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M15 3v18"}],["path",{d:"m8 9 3 3-3 3"}]],a0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M15 14v1"}],["path",{d:"M15 19v2"}],["path",{d:"M15 3v2"}],["path",{d:"M15 9v1"}]],Nv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M15 3v18"}],["path",{d:"m10 15-3-3 3-3"}]],Kv=[["path",{d:"M14 15h1"}],["path",{d:"M14 9h1"}],["path",{d:"M19 15h2"}],["path",{d:"M19 9h2"}],["path",{d:"M3 15h2"}],["path",{d:"M3 9h2"}],["path",{d:"M9 15h1"}],["path",{d:"M9 9h1"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],Jv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M15 3v18"}]],Qv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"m9 16 3-3 3 3"}]],h0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M14 9h1"}],["path",{d:"M19 9h2"}],["path",{d:"M3 9h2"}],["path",{d:"M9 9h1"}]],jv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"m15 14-3 3-3-3"}]],Yv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 9h18"}]],_v=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M9 3v18"}],["path",{d:"M9 15h12"}]],xv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 15h12"}],["path",{d:"M15 3v18"}]],t0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"M9 21V9"}]],a$=[["path",{d:"M5.364 3.848C4 6 3 9.652 3 12.652V19a2 2 0 002 2h14a2 2 0 002-2v-5c0-2.334-1.816-4.668-2.622-7.002"}],["path",{d:"M7 3h11.379a2 2 0 011.789 1.106l.723 1.447A1 1 0 0119.997 7h-8.525a2 2 0 01-1.789-1.106L8.79 4.105a2 2 0 10-3.579 1.789l2.261 4.522A5 5 0 018 12.652V21"}]],h$=[["path",{d:"m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"}]],t$=[["path",{d:"M12.5 11.134 18.196 21"}],["path",{d:"M20.425 5.299a10 10 0 0 0-16.941 9.78c.183.563.843.774 1.355.478L20.16 6.711c.512-.296.66-.973.264-1.413"}],["path",{d:"M21 21H3"}]],d$=[["path",{d:"M8 21s-4-3-4-9 4-9 4-9"}],["path",{d:"M16 3s4 3 4 9-4 9-4 9"}]],c$=[["path",{d:"M12 18h10"}],["path",{d:"M13.248 9.998A4.5 4.5 0 0 0 11.75 8.6V8a1 1 0 0 0-7.5 0 4.9 4.9 0 0 0 2.25 9H8"}],["path",{d:"m15 14-2 6"}],["path",{d:"m19 14 2 6"}],["path",{d:"M21 14h-8"}],["path",{d:"M8 20v-5.922a2 2 0 0 0-.586-1.414L6.5 11.75"}],["path",{d:"M9.205 12.795 8 14"}],["circle",{cx:"19",cy:"6",r:"2"}]],M$=[["path",{d:"M11 15h2"}],["path",{d:"M12 12v3"}],["path",{d:"M12 19v3"}],["path",{d:"M15.282 19a1 1 0 0 0 .948-.68l2.37-6.988a7 7 0 1 0-13.2 0l2.37 6.988a1 1 0 0 0 .948.68z"}],["path",{d:"M9 9a3 3 0 1 1 6 0"}]],p$=[["path",{d:"M5.8 11.3 2 22l10.7-3.79"}],["path",{d:"M4 3h.01"}],["path",{d:"M22 8h.01"}],["path",{d:"M15 2h.01"}],["path",{d:"M22 20h.01"}],["path",{d:"m22 2-2.24.75a2.9 2.9 0 0 0-1.96 3.12c.1.86-.57 1.63-1.45 1.63h-.38c-.86 0-1.6.6-1.76 1.44L14 10"}],["path",{d:"m22 13-.82-.33c-.86-.34-1.82.2-1.98 1.11c-.11.7-.72 1.22-1.43 1.22H17"}],["path",{d:"m11 2 .33.82c.34.86-.2 1.82-1.11 1.98C9.52 4.9 9 5.52 9 6.23V7"}],["path",{d:"M11 13c1.93 1.93 2.83 4.17 2 5-.83.83-3.07-.07-5-2-1.93-1.93-2.83-4.17-2-5 .83-.83 3.07.07 5 2Z"}]],l$=[["rect",{x:"14",y:"3",width:"5",height:"18",rx:"1"}],["rect",{x:"5",y:"3",width:"5",height:"18",rx:"1"}]],i$=[["circle",{cx:"11",cy:"4",r:"2"}],["circle",{cx:"18",cy:"8",r:"2"}],["circle",{cx:"20",cy:"16",r:"2"}],["path",{d:"M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z"}]],n$=[["rect",{width:"14",height:"20",x:"5",y:"2",rx:"2"}],["path",{d:"M15 14h.01"}],["path",{d:"M9 6h6"}],["path",{d:"M9 10h6"}]],d0=[["path",{d:"M13 21h8"}],["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}]],e$=[["path",{d:"m10 10-6.157 6.162a2 2 0 0 0-.5.833l-1.322 4.36a.5.5 0 0 0 .622.624l4.358-1.323a2 2 0 0 0 .83-.5L14 13.982"}],["path",{d:"m12.829 7.172 4.359-4.346a1 1 0 1 1 3.986 3.986l-4.353 4.353"}],["path",{d:"m2 2 20 20"}]],r$=[["path",{d:"m18 13-.654-4.736a4 4 0 00-2.756-3.266l-9.163-2.9a2 2 0 00-2.017.493l-.82.819a2 2 0 00-.492 2.017l2.9 9.163a4 4 0 003.266 2.756L13 18"}],["path",{d:"M18.648 12.352a1.205 1.205 0 011.704 0l1.296 1.296a1.205 1.205 0 010 1.704l-6.296 6.296a1.205 1.205 0 01-1.704 0l-1.296-1.296a1.205 1.205 0 010-1.704z"}],["path",{d:"m3 3 6.586 6.586"}],["circle",{cx:"11",cy:"11",r:"2"}]],c0=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}]],o$=[["path",{d:"M13 21h8"}],["path",{d:"m15 5 4 4"}],["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}]],v$=[["path",{d:"m10 10-6.157 6.162a2 2 0 0 0-.5.833l-1.322 4.36a.5.5 0 0 0 .622.624l4.358-1.323a2 2 0 0 0 .83-.5L14 13.982"}],["path",{d:"m12.829 7.172 4.359-4.346a1 1 0 1 1 3.986 3.986l-4.353 4.353"}],["path",{d:"m15 5 4 4"}],["path",{d:"m2 2 20 20"}]],$$=[["path",{d:"M13 7 8.7 2.7a2.41 2.41 0 0 0-3.4 0L2.7 5.3a2.41 2.41 0 0 0 0 3.4L7 13"}],["path",{d:"m8 6 2-2"}],["path",{d:"m18 16 2-2"}],["path",{d:"m17 11 4.3 4.3c.94.94.94 2.46 0 3.4l-2.6 2.6c-.94.94-2.46.94-3.4 0L11 17"}],["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}],["path",{d:"m15 5 4 4"}]],m$=[["path",{d:"M10 3H8"}],["path",{d:"m15.007 5.008 3.987 3.986"}],["path",{d:"M20 15v4"}],["path",{d:"M21.174 6.813a2.82 2.82 0 0 0-3.986-3.987L3.842 16.175a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}],["path",{d:"M22 17h-4"}],["path",{d:"M4 5v4"}],["path",{d:"M6 7H2"}],["path",{d:"M9 2v2"}]],y$=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}],["path",{d:"m15 5 4 4"}]],s$=[["path",{d:"M10.83 2.38a2 2 0 0 1 2.34 0l8 5.74a2 2 0 0 1 .73 2.25l-3.04 9.26a2 2 0 0 1-1.9 1.37H7.04a2 2 0 0 1-1.9-1.37L2.1 10.37a2 2 0 0 1 .73-2.25z"}]],C$=[["line",{x1:"19",x2:"5",y1:"5",y2:"19"}],["circle",{cx:"6.5",cy:"6.5",r:"2.5"}],["circle",{cx:"17.5",cy:"17.5",r:"2.5"}]],u$=[["path",{d:"M 5 22 H 11.072"}],["path",{d:"M15 5 C 23 4 23.328 12.676 20 14.536"}],["path",{d:"M15.802 10.308 C 14.776 9.465 14 8.496 14 7"}],["path",{d:"M2 2h.01"}],["path",{d:"M2 7h.01"}],["path",{d:"M5 4.5h.01"}],["path",{d:"M8.127 22 A 8 8 0 0 1 4 15 3 3 0 0 1 4.879 12.879C 6.879 10.879 10 10 10 7"}],["circle",{cx:"18",cy:"18",r:"4"}],["rect",{x:"9",y:"2",width:"6",height:"5",rx:"1"}]],g$=[["path",{d:"M12 2v20"}],["circle",{cx:"12",cy:"12",r:"7"}]],A$=[["circle",{cx:"12",cy:"5",r:"1"}],["path",{d:"m9 20 3-6 3 6"}],["path",{d:"m6 8 6 2 6-2"}],["path",{d:"M12 10v4"}]],H$=[["path",{d:"M20 11H4"}],["path",{d:"M20 7H4"}],["path",{d:"M7 21V4a1 1 0 0 1 1-1h4a1 1 0 0 1 0 12H7"}]],V$=[["path",{d:"M13 2a9 9 0 0 1 9 9"}],["path",{d:"M13 6a5 5 0 0 1 5 5"}],["path",{d:"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"}]],S$=[["path",{d:"M16 2v6h6"}],["path",{d:"m22 2-6 6"}],["path",{d:"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"}]],w$=[["path",{d:"M14 6h8"}],["path",{d:"m18 2 4 4-4 4"}],["path",{d:"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"}]],L$=[["path",{d:"m16 2 6 6"}],["path",{d:"m22 2-6 6"}],["path",{d:"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"}]],f$=[["path",{d:"M13.832 16.568a1 1 0 001.213-.303l.355-.465A2 2 0 0117 15h3a2 2 0 012 2v3a2 2 0 01-2 2A18 18 0 012 4a2 2 0 012-2h3a2 2 0 012 2v3a2 2 0 01-.8 1.6l-.468.351a1 1 0 00-.292 1.233 14 14 0 006.392 6.384"}],["path",{d:"M14.396 4.171h7.171"}],["path",{d:"M17.343 8.17h4.224"}]],k$=[["path",{d:"M10.1 13.9a14 14 0 0 0 3.732 2.668 1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2 18 18 0 0 1-12.728-5.272"}],["path",{d:"M22 2 2 22"}],["path",{d:"M4.76 13.582A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 .244.473"}]],P$=[["path",{d:"m16 8 6-6"}],["path",{d:"M22 8V2h-6"}],["path",{d:"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"}]],B$=[["path",{d:"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"}]],z$=[["line",{x1:"9",x2:"9",y1:"4",y2:"20"}],["path",{d:"M4 7c0-1.7 1.3-3 3-3h13"}],["path",{d:"M18 20c-1.7 0-3-1.3-3-3V4"}]],D$=[["path",{d:"M10 13v4"}],["path",{d:"M14 13v4"}],["path",{d:"M18 13v4"}],["path",{d:"M2 13h20"}],["path",{d:"M22 11.5A3.5 3.5 0 0018.5 8a3.52 3.52 0 01-3.173-2A7 7 0 002 9v10a2 2 0 002 2h16a2 2 0 002-2z"}],["path",{d:"M6 13v4"}]],F$=[["path",{d:"m14 13-8.381 8.38a1 1 0 0 1-3.001-3L11 9.999"}],["path",{d:"M15.973 4.027A13 13 0 0 0 5.902 2.373c-1.398.342-1.092 2.158.277 2.601a19.9 19.9 0 0 1 5.822 3.024"}],["path",{d:"M16.001 11.999a19.9 19.9 0 0 1 3.024 5.824c.444 1.369 2.26 1.676 2.603.278A13 13 0 0 0 20 8.069"}],["path",{d:"M18.352 3.352a1.205 1.205 0 0 0-1.704 0l-5.296 5.296a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l5.296-5.296a1.205 1.205 0 0 0 0-1.704z"}]],R$=[["path",{d:"M21 9V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h4"}],["rect",{width:"10",height:"7",x:"12",y:"13",rx:"2"}]],b$=[["path",{d:"M2 10h6V4"}],["path",{d:"m2 4 6 6"}],["path",{d:"M21 10V7a2 2 0 0 0-2-2h-7"}],["path",{d:"M3 14v2a2 2 0 0 0 2 2h3"}],["rect",{x:"12",y:"14",width:"10",height:"7",rx:"1"}]],T$=[["path",{d:"M11 17h3v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-3a3.16 3.16 0 0 0 2-2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1h-1a5 5 0 0 0-2-4V3a4 4 0 0 0-3.2 1.6l-.3.4H11a6 6 0 0 0-6 6v1a5 5 0 0 0 2 4v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"}],["path",{d:"M16 10h.01"}],["path",{d:"M2 8v1a2 2 0 0 0 2 2h1"}]],q$=[["path",{d:"M14 3v11"}],["path",{d:"M14 9h-3a3 3 0 0 1 0-6h9"}],["path",{d:"M18 3v11"}],["path",{d:"M22 18H2l4-4"}],["path",{d:"m6 22-4-4"}]],U$=[["path",{d:"M10 3v11"}],["path",{d:"M10 9H7a1 1 0 0 1 0-6h8"}],["path",{d:"M14 3v11"}],["path",{d:"m18 14 4 4H2"}],["path",{d:"m22 18-4 4"}]],O$=[["path",{d:"M13 4v16"}],["path",{d:"M17 4v16"}],["path",{d:"M19 4H9.5a4.5 4.5 0 0 0 0 9H13"}]],G$=[["path",{d:"M18 11h-4a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h4"}],["path",{d:"M6 7v13a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7"}],["rect",{width:"16",height:"5",x:"4",y:"2",rx:"1"}]],Z$=[["path",{d:"M12 17v5"}],["path",{d:"M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89"}],["path",{d:"m2 2 20 20"}],["path",{d:"M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11"}]],E$=[["path",{d:"m10.5 20.5 10-10a4.95 4.95 0 1 0-7-7l-10 10a4.95 4.95 0 1 0 7 7Z"}],["path",{d:"m8.5 8.5 7 7"}]],W$=[["path",{d:"M12 17v5"}],["path",{d:"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"}]],I$=[["path",{d:"m12 14-1 1"}],["path",{d:"m13.75 18.25-1.25 1.42"}],["path",{d:"M17.775 5.654a15.68 15.68 0 0 0-12.121 12.12"}],["path",{d:"M18.8 9.3a1 1 0 0 0 2.1 7.7"}],["path",{d:"M21.964 20.732a1 1 0 0 1-1.232 1.232l-18-5a1 1 0 0 1-.695-1.232A19.68 19.68 0 0 1 15.732 2.037a1 1 0 0 1 1.232.695z"}]],X$=[["path",{d:"M2 22h20"}],["path",{d:"M3.77 10.77 2 9l2-4.5 1.1.55c.55.28.9.84.9 1.45s.35 1.17.9 1.45L8 8.5l3-6 1.05.53a2 2 0 0 1 1.09 1.52l.72 5.4a2 2 0 0 0 1.09 1.52l4.4 2.2c.42.22.78.55 1.01.96l.6 1.03c.49.88-.06 1.98-1.06 2.1l-1.18.15c-.47.06-.95-.02-1.37-.24L4.29 11.15a2 2 0 0 1-.52-.38Z"}]],N$=[["path",{d:"m12 9-8.414 8.414A2 2 0 0 0 3 18.828v1.344a2 2 0 0 1-.586 1.414A2 2 0 0 1 3.828 21h1.344a2 2 0 0 0 1.414-.586L15 12"}],["path",{d:"m18 9 .4.4a1 1 0 1 1-3 3l-3.8-3.8a1 1 0 1 1 3-3l.4.4 3.4-3.4a1 1 0 1 1 3 3z"}],["path",{d:"m2 22 .414-.414"}]],K$=[["path",{d:"M2 22h20"}],["path",{d:"M6.36 17.4 4 17l-2-4 1.1-.55a2 2 0 0 1 1.8 0l.17.1a2 2 0 0 0 1.8 0L8 12 5 6l.9-.45a2 2 0 0 1 2.09.2l4.02 3a2 2 0 0 0 2.1.2l4.19-2.06a2.41 2.41 0 0 1 1.73-.17L21 7a1.4 1.4 0 0 1 .87 1.99l-.38.76c-.23.46-.6.84-1.07 1.08L7.58 17.2a2 2 0 0 1-1.22.18Z"}]],J$=[["path",{d:"M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.1-1.1.5l-.3.5c-.2.5-.1 1 .3 1.3L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.5 5.3c.3.4.8.5 1.3.3l.5-.2c.4-.3.6-.7.5-1.2z"}]],Q$=[["path",{d:"M14 8.536V6a4 4 0 014-4h1.5a.5.5 0 01.5.5V4a4 4 0 01-4 4 4 4 0 00-4 4 5 5 0 01-8-4 5 5 0 018 4c0 2 1 3 1 5"}],["path",{d:"m18 17-1.085 3.58A2 2 0 0115 22H9.002a2 2 0 01-1.913-1.418L6 17"}],["path",{d:"M5 17h14"}]],j$=[["path",{d:"m10.215 4.56 9.79 5.71a2 2 0 0 1 .003 3.458l-.393.23"}],["path",{d:"m16.042 16.042-8.034 4.686A2 2 0 0 1 5 19V5"}],["path",{d:"m2 2 20 20"}]],Y$=[["path",{d:"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"}]],_$=[["path",{d:"M12.832 8.445a1 1 0 00-1.589-.098l-2.075 3.098a1 1 0 000 1.11l2 3a1 1 0 001.664 0l2-3a1 1 0 000-1.11z"}],["rect",{x:"5",y:"2",width:"14",height:"20",rx:"2"}]],x$=[["path",{d:"M14.832 8.445a1 1 0 00-1.589-.098l-2.075 3.098a1 1 0 000 1.11l2 3a1 1 0 001.664 0l2-3a1 1 0 000-1.11z"}],["path",{d:"m7.18 20.827-5-11a2 2 0 01.993-2.647L7 5.44"}],["rect",{x:"7",y:"2",width:"14",height:"20",rx:"2"}]],am=[["path",{d:"M12.65 7.65a2 2 0 012.629-1.046l5.51 2.374a2 2 0 011.046 2.628l-3.957 9.184a2 2 0 01-2.628 1.046l-5.51-2.374a2 2 0 01-1.046-2.628z"}],["path",{d:"M18 7.777V4a2 2 0 00-2-2h-6a2 2 0 00-2 2v10a2 2 0 001.137 1.805"}],["path",{d:"m8 4.389-4.364.809a2 2 0 00-1.602 2.33l1.822 9.833a2 2 0 002.331 1.602l2.542-.47"}]],hm=[["path",{d:"M9 2v6"}],["path",{d:"M15 2v6"}],["path",{d:"M12 17v5"}],["path",{d:"M5 8h14"}],["path",{d:"M6 11V8h12v3a6 6 0 1 1-12 0Z"}]],M0=[["path",{d:"M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z"}],["path",{d:"m2 22 3-3"}],["path",{d:"M7.5 13.5 10 11"}],["path",{d:"M10.5 16.5 13 14"}],["path",{d:"m18 3-4 4h6l-4 4"}]],tm=[["path",{d:"M12 22v-5"}],["path",{d:"M15 8V2"}],["path",{d:"M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z"}],["path",{d:"M9 8V2"}]],dm=[["path",{d:"M5 12h14"}],["path",{d:"M12 5v14"}]],cm=[["path",{d:"M3 2v1c0 1 2 1 2 2S3 6 3 7s2 1 2 2-2 1-2 2 2 1 2 2"}],["path",{d:"M18 6h.01"}],["path",{d:"M6 18h.01"}],["path",{d:"M20.83 8.83a4 4 0 0 0-5.66-5.66l-12 12a4 4 0 1 0 5.66 5.66Z"}],["path",{d:"M18 11.66V22a4 4 0 0 0 4-4V6"}]],Mm=[["path",{d:"M12 6V2h-1"}],["path",{d:"M9 15a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1"}],["path",{d:"M9 21V11a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v10"}]],pm=[["path",{d:"M10 4.5V4a2 2 0 0 0-2.41-1.957"}],["path",{d:"M13.9 8.4a2 2 0 0 0-1.26-1.295"}],["path",{d:"M21.7 16.2A8 8 0 0 0 22 14v-3a2 2 0 1 0-4 0v-1a2 2 0 0 0-3.63-1.158"}],["path",{d:"m7 15-1.8-1.8a2 2 0 0 0-2.79 2.86L6 19.7a7.74 7.74 0 0 0 6 2.3h2a8 8 0 0 0 5.657-2.343"}],["path",{d:"M6 6v8"}],["path",{d:"m2 2 20 20"}]],lm=[["path",{d:"M22 14a8 8 0 0 1-8 8"}],["path",{d:"M18 11v-1a2 2 0 0 0-2-2a2 2 0 0 0-2 2"}],["path",{d:"M14 10V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1"}],["path",{d:"M10 9.5V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v10"}],["path",{d:"M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"}]],im=[["path",{d:"M18 8a2 2 0 0 0 0-4 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0 0 4"}],["path",{d:"M10 22 9 8"}],["path",{d:"m14 22 1-14"}],["path",{d:"M20 8c.5 0 .9.4.8 1l-2.6 12c-.1.5-.7 1-1.2 1H7c-.6 0-1.1-.4-1.2-1L3.2 9c-.1-.6.3-1 .8-1Z"}]],nm=[["path",{d:"M18.6 14.4c.8-.8.8-2 0-2.8l-8.1-8.1a4.95 4.95 0 1 0-7.1 7.1l8.1 8.1c.9.7 2.1.7 2.9-.1Z"}],["path",{d:"m22 22-5.5-5.5"}]],em=[["path",{d:"M18 7c0-5.333-8-5.333-8 0"}],["path",{d:"M10 7v14"}],["path",{d:"M6 21h12"}],["path",{d:"M6 13h10"}]],rm=[["path",{d:"M18.36 6.64A9 9 0 0 1 20.77 15"}],["path",{d:"M6.16 6.16a9 9 0 1 0 12.68 12.68"}],["path",{d:"M12 2v4"}],["path",{d:"m2 2 20 20"}]],om=[["path",{d:"M12 2v10"}],["path",{d:"M18.4 6.6a9 9 0 1 1-12.77.04"}]],vm=[["path",{d:"M2 3h20"}],["path",{d:"M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3"}],["path",{d:"m7 21 5-5 5 5"}]],$m=[["path",{d:"M10 11v1"}],["path",{d:"M12 8h8"}],["path",{d:"M15 20v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3"}],["path",{d:"M4 20h16"}],["path",{d:"M4 22V4a2 2 0 012-2h12a2 2 0 012 2v18"}],["path",{d:"M4 8h4"}],["path",{d:"M8.635 10.093A2 2 0 018 8.631V7a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-.293.707l-1 1a1 1 0 01-1.414 0z"}]],mm=[["path",{d:"M13.5 22H7a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v.5"}],["path",{d:"m16 19 2 2 4-4"}],["path",{d:"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v2"}],["path",{d:"M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6"}]],ym=[["path",{d:"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"}],["path",{d:"M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6"}],["rect",{x:"6",y:"14",width:"12",height:"8",rx:"1"}]],sm=[["path",{d:"M12.531 22H7a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6.377"}],["path",{d:"m16.5 16.5 5 5"}],["path",{d:"m16.5 21.5 5-5"}],["path",{d:"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.5"}],["path",{d:"M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6"}]],Cm=[["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2"}],["path",{d:"M12 9v11"}],["path",{d:"M2 9h13a2 2 0 0 1 2 2v9"}]],um=[["path",{d:"M5 7 3 5"}],["path",{d:"M9 6V3"}],["path",{d:"m13 7 2-2"}],["circle",{cx:"9",cy:"13",r:"3"}],["path",{d:"M11.83 12H20a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h2.17"}],["path",{d:"M16 16h2"}]],gm=[["path",{d:"M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z"}]],Am=[["path",{d:"M2.5 16.88a1 1 0 0 1-.32-1.43l9-13.02a1 1 0 0 1 1.64 0l9 13.01a1 1 0 0 1-.32 1.44l-8.51 4.86a2 2 0 0 1-1.98 0Z"}],["path",{d:"M12 2v20"}]],Hm=[["rect",{width:"5",height:"5",x:"3",y:"3",rx:"1"}],["rect",{width:"5",height:"5",x:"16",y:"3",rx:"1"}],["rect",{width:"5",height:"5",x:"3",y:"16",rx:"1"}],["path",{d:"M21 16h-3a2 2 0 0 0-2 2v3"}],["path",{d:"M21 21v.01"}],["path",{d:"M12 7v3a2 2 0 0 1-2 2H7"}],["path",{d:"M3 12h.01"}],["path",{d:"M12 3h.01"}],["path",{d:"M12 16v.01"}],["path",{d:"M16 12h1"}],["path",{d:"M21 12v.01"}],["path",{d:"M12 21v-1"}]],Vm=[["path",{d:"M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z"}],["path",{d:"M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z"}]],Sm=[["path",{d:"M13 16a3 3 0 0 1 2.24 5"}],["path",{d:"M18 12h.01"}],["path",{d:"M18 21h-8a4 4 0 0 1-4-4 7 7 0 0 1 7-7h.2L9.6 6.4a1 1 0 1 1 2.8-2.8L15.8 7h.2c3.3 0 6 2.7 6 6v1a2 2 0 0 1-2 2h-1a3 3 0 0 0-3 3"}],["path",{d:"M20 8.54V4a2 2 0 1 0-4 0v3"}],["path",{d:"M7.612 12.524a3 3 0 1 0-1.6 4.3"}]],wm=[["path",{d:"M12 12h.01"}],["path",{d:"M14 15.4641a4 4 0 0 1-4 0L7.52786 19.74597 A 1 1 0 0 0 7.99303 21.16211 10 10 0 0 0 16.00697 21.16211 1 1 0 0 0 16.47214 19.74597z"}],["path",{d:"M16 12a4 4 0 0 0-2-3.464l2.472-4.282a1 1 0 0 1 1.46-.305 10 10 0 0 1 4.006 6.94A1 1 0 0 1 21 12z"}],["path",{d:"M8 12a4 4 0 0 1 2-3.464L7.528 4.254a1 1 0 0 0-1.46-.305 10 10 0 0 0-4.006 6.94A1 1 0 0 0 3 12z"}]],Lm=[["path",{d:"M19.07 4.93A10 10 0 0 0 6.99 3.34"}],["path",{d:"M4 6h.01"}],["path",{d:"M2.29 9.62A10 10 0 1 0 21.31 8.35"}],["path",{d:"M16.24 7.76A6 6 0 1 0 8.23 16.67"}],["path",{d:"M12 18h.01"}],["path",{d:"M17.99 11.66A6 6 0 0 1 15.77 16.67"}],["circle",{cx:"12",cy:"12",r:"2"}],["path",{d:"m13.41 10.59 5.66-5.66"}]],fm=[["path",{d:"M3 12h3.28a1 1 0 0 1 .948.684l2.298 7.934a.5.5 0 0 0 .96-.044L13.82 4.771A1 1 0 0 1 14.792 4H21"}]],km=[["path",{d:"M13.414 13.414a2 2 0 1 1-2.828-2.828"}],["path",{d:"M16.247 7.761a6 6 0 0 1 1.744 4.572"}],["path",{d:"M19.075 4.933a10 10 0 0 1 2.234 10.72"}],["path",{d:"m2 2 20 20"}],["path",{d:"M4.925 19.067a10 10 0 0 1 0-14.134"}],["path",{d:"M7.753 16.239a6 6 0 0 1 0-8.478"}]],Pm=[["path",{d:"M5 16v2"}],["path",{d:"M19 16v2"}],["rect",{width:"20",height:"8",x:"2",y:"8",rx:"2"}],["path",{d:"M18 12h.01"}]],Bm=[["path",{d:"M4.9 16.1C1 12.2 1 5.8 4.9 1.9"}],["path",{d:"M7.8 4.7a6.14 6.14 0 0 0-.8 7.5"}],["circle",{cx:"12",cy:"9",r:"2"}],["path",{d:"M16.2 4.8c2 2 2.26 5.11.8 7.47"}],["path",{d:"M19.1 1.9a9.96 9.96 0 0 1 0 14.1"}],["path",{d:"M9.5 18h5"}],["path",{d:"m8 22 4-11 4 11"}]],zm=[["path",{d:"M16.247 7.761a6 6 0 0 1 0 8.478"}],["path",{d:"M19.075 4.933a10 10 0 0 1 0 14.134"}],["path",{d:"M4.925 19.067a10 10 0 0 1 0-14.134"}],["path",{d:"M7.753 16.239a6 6 0 0 1 0-8.478"}],["circle",{cx:"12",cy:"12",r:"2"}]],Dm=[["path",{d:"M20.34 17.52a10 10 0 1 0-2.82 2.82"}],["circle",{cx:"19",cy:"19",r:"2"}],["path",{d:"m13.41 13.41 4.18 4.18"}],["circle",{cx:"12",cy:"12",r:"2"}]],Fm=[["path",{d:"M22 17a10 10 0 0 0-20 0"}],["path",{d:"M6 17a6 6 0 0 1 12 0"}],["path",{d:"M10 17a2 2 0 0 1 4 0"}]],Rm=[["rect",{width:"12",height:"20",x:"6",y:"2",rx:"2"}],["rect",{width:"20",height:"12",x:"2",y:"6",rx:"2"}]],bm=[["path",{d:"M13 22H4a2 2 0 0 1 0-4h12"}],["path",{d:"M13.236 18a3 3 0 0 0-2.2-5"}],["path",{d:"M16 9h.01"}],["path",{d:"M16.82 3.94a3 3 0 1 1 3.237 4.868l1.815 2.587a1.5 1.5 0 0 1-1.5 2.1l-2.872-.453a3 3 0 0 0-3.5 3"}],["path",{d:"M17 4.988a3 3 0 1 0-5.2 2.052A7 7 0 0 0 4 14.015 4 4 0 0 0 8 18"}]],Tm=[["path",{d:"M12 7v10"}],["path",{d:"M14.828 14.829a4 4 0 0 1-5.656 0 4 4 0 0 1 0-5.657 4 4 0 0 1 5.656 0"}],["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}]],qm=[["path",{d:"M15.828 14.829a4 4 0 0 1-5.656 0 4 4 0 0 1 0-5.657 4 4 0 0 1 5.656 0"}],["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}],["path",{d:"M8 12h5"}]],Um=[["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}],["path",{d:"M8 11h8"}],["path",{d:"M8 7h8"}],["path",{d:"M9 7a4 4 0 0 1 0 8H8l3 2"}]],Om=[["path",{d:"m12 10 3-3"}],["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}],["path",{d:"M9 11h6"}],["path",{d:"M9 15h6"}],["path",{d:"m9 7 3 3v7"}]],Gm=[["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}],["path",{d:"M8 11h5a2 2 0 0 0 0-4h-3v10"}],["path",{d:"M8 15h5"}]],Zm=[["path",{d:"M10 17V9.5a1 1 0 0 1 5 0"}],["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}],["path",{d:"M8 13h5"}],["path",{d:"M8 17h7"}]],Em=[["path",{d:"M10 11h4"}],["path",{d:"M10 17V7h5"}],["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}],["path",{d:"M8 15h5"}]],Wm=[["path",{d:"M13 16H8"}],["path",{d:"M14 8H8"}],["path",{d:"M16 12H8"}],["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}]],Im=[["path",{d:"M10 7v10a5 5 0 0 0 5-5"}],["path",{d:"m14 8-6 3"}],["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}]],Xm=[["path",{d:"M12 17V7"}],["path",{d:"M16 8h-6a2 2 0 0 0 0 4h4a2 2 0 0 1 0 4H8"}],["path",{d:"M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"}]],Nm=[["path",{d:"M14 4v16H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z"}],["circle",{cx:"14",cy:"12",r:"8"}]],p0=[["rect",{width:"20",height:"12",x:"2",y:"6",rx:"2"}],["path",{d:"M12 12h.01"}],["path",{d:"M17 12h.01"}],["path",{d:"M7 12h.01"}]],Km=[["path",{d:"M20 6a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-4a2 2 0 0 1-1.6-.8l-1.6-2.13a1 1 0 0 0-1.6 0L9.6 17.2A2 2 0 0 1 8 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z"}]],Jm=[["rect",{width:"20",height:"12",x:"2",y:"6",rx:"2"}]],Qm=[["rect",{width:"12",height:"20",x:"6",y:"2",rx:"2"}]],jm=[["path",{d:"M7 19H4.815a1.83 1.83 0 0 1-1.57-.881 1.785 1.785 0 0 1-.004-1.784L7.196 9.5"}],["path",{d:"M11 19h8.203a1.83 1.83 0 0 0 1.556-.89 1.784 1.784 0 0 0 0-1.775l-1.226-2.12"}],["path",{d:"m14 16-3 3 3 3"}],["path",{d:"M8.293 13.596 7.196 9.5 3.1 10.598"}],["path",{d:"m9.344 5.811 1.093-1.892A1.83 1.83 0 0 1 11.985 3a1.784 1.784 0 0 1 1.546.888l3.943 6.843"}],["path",{d:"m13.378 9.633 4.096 1.098 1.097-4.096"}]],Ym=[["path",{d:"m15 14 5-5-5-5"}],["path",{d:"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13"}]],_m=[["circle",{cx:"12",cy:"17",r:"1"}],["path",{d:"M21 7v6h-6"}],["path",{d:"M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"}]],xm=[["path",{d:"M21 7v6h-6"}],["path",{d:"M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"}]],ay=[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"}],["path",{d:"M3 3v5h5"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"}],["path",{d:"M16 16h5v5"}],["circle",{cx:"12",cy:"12",r:"1"}]],hy=[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"}],["path",{d:"M3 3v5h5"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"}],["path",{d:"M16 16h5v5"}]],ty=[["path",{d:"M21 8L18.74 5.74A9.75 9.75 0 0 0 12 3C11 3 10.03 3.16 9.13 3.47"}],["path",{d:"M8 16H3v5"}],["path",{d:"M3 12C3 9.51 4 7.26 5.64 5.64"}],["path",{d:"m3 16 2.26 2.26A9.75 9.75 0 0 0 12 21c2.49 0 4.74-1 6.36-2.64"}],["path",{d:"M21 12c0 1-.16 1.97-.47 2.87"}],["path",{d:"M21 3v5h-5"}],["path",{d:"M22 22 2 2"}]],dy=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"}],["path",{d:"M21 3v5h-5"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"}],["path",{d:"M8 16H3v5"}]],cy=[["path",{d:"M5 6a4 4 0 0 1 4-4h6a4 4 0 0 1 4 4v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6Z"}],["path",{d:"M5 10h14"}],["path",{d:"M15 7v6"}]],My=[["path",{d:"M4 7V4h16v3"}],["path",{d:"M5 20h6"}],["path",{d:"M13 4 8 20"}],["path",{d:"m15 15 5 5"}],["path",{d:"m20 15-5 5"}]],py=[["path",{d:"M17 3v10"}],["path",{d:"m12.67 5.5 8.66 5"}],["path",{d:"m12.67 10.5 8.66-5"}],["path",{d:"M9 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2z"}]],ly=[["path",{d:"m2 9 3-3 3 3"}],["path",{d:"M13 18H7a2 2 0 0 1-2-2V6"}],["path",{d:"m22 15-3 3-3-3"}],["path",{d:"M11 6h6a2 2 0 0 1 2 2v10"}]],iy=[["path",{d:"m17 2 4 4-4 4"}],["path",{d:"M3 11v-1a4 4 0 0 1 4-4h14"}],["path",{d:"m7 22-4-4 4-4"}],["path",{d:"M21 13v1a4 4 0 0 1-4 4H3"}],["path",{d:"M11 10h1v4"}]],ny=[["path",{d:"M11.656 6H21l-4-4"}],["path",{d:"M17.898 17.898A4 4 0 0 1 17 18H3l4-4"}],["path",{d:"m2 2 20 20"}],["path",{d:"M21 13v1a4 4 0 0 1-.171 1.159"}],["path",{d:"m21 6-4 4"}],["path",{d:"M3 11v-1a4 4 0 0 1 3.102-3.898"}],["path",{d:"m7 22-4-4"}]],ey=[["path",{d:"m17 2 4 4-4 4"}],["path",{d:"M3 11v-1a4 4 0 0 1 4-4h14"}],["path",{d:"m7 22-4-4 4-4"}],["path",{d:"M21 13v1a4 4 0 0 1-4 4H3"}]],ry=[["path",{d:"M14 14a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1"}],["path",{d:"M14 4a1 1 0 0 1 1-1"}],["path",{d:"M15 10a1 1 0 0 1-1-1"}],["path",{d:"M19 14a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1"}],["path",{d:"M21 4a1 1 0 0 0-1-1"}],["path",{d:"M21 9a1 1 0 0 1-1 1"}],["path",{d:"m3 7 3 3 3-3"}],["path",{d:"M6 10V5a2 2 0 0 1 2-2h2"}],["rect",{x:"3",y:"14",width:"7",height:"7",rx:"1"}]],oy=[["path",{d:"M14 4a1 1 0 0 1 1-1"}],["path",{d:"M15 10a1 1 0 0 1-1-1"}],["path",{d:"M21 4a1 1 0 0 0-1-1"}],["path",{d:"M21 9a1 1 0 0 1-1 1"}],["path",{d:"m3 7 3 3 3-3"}],["path",{d:"M6 10V5a2 2 0 0 1 2-2h2"}],["rect",{x:"3",y:"14",width:"7",height:"7",rx:"1"}]],vy=[["path",{d:"m12 17-5-5 5-5"}],["path",{d:"M22 18v-2a4 4 0 0 0-4-4H7"}],["path",{d:"m7 17-5-5 5-5"}]],$y=[["path",{d:"M20 18v-2a4 4 0 0 0-4-4H4"}],["path",{d:"m9 17-5-5 5-5"}]],my=[["path",{d:"M12 6a2 2 0 0 0-3.414-1.414l-6 6a2 2 0 0 0 0 2.828l6 6A2 2 0 0 0 12 18z"}],["path",{d:"M22 6a2 2 0 0 0-3.414-1.414l-6 6a2 2 0 0 0 0 2.828l6 6A2 2 0 0 0 22 18z"}]],yy=[["path",{d:"M12 11.22C11 9.997 10 9 10 8a2 2 0 0 1 4 0c0 1-.998 2.002-2.01 3.22"}],["path",{d:"m12 18 2.57-3.5"}],["path",{d:"M6.243 9.016a7 7 0 0 1 11.507-.009"}],["path",{d:"M9.35 14.53 12 11.22"}],["path",{d:"M9.35 14.53C7.728 12.246 6 10.221 6 7a6 5 0 0 1 12 0c-.005 3.22-1.778 5.235-3.43 7.5l3.557 4.527a1 1 0 0 1-.203 1.43l-1.894 1.36a1 1 0 0 1-1.384-.215L12 18l-2.679 3.593a1 1 0 0 1-1.39.213l-1.865-1.353a1 1 0 0 1-.203-1.422z"}]],sy=[["path",{d:"M12 17v4"}],["path",{d:"M12 5V3"}],["path",{d:"M12 9v3"}],["path",{d:"M2.077 18.449A2 2 0 0 0 4 21h16a2 2 0 0 0 1.924-2.55l-4-14A2 2 0 0 0 16 3H8a2 2 0 0 0-1.924 1.45z"}]],Cy=[["path",{d:"M12 21 7.5 8.322"}],["path",{d:"m14 7 1.75-3.767a.5.5 0 0 1 .662-.172L20 5.005"}],["path",{d:"m20 8.998-3.588 1.944a.5.5 0 0 1-.662-.172L14 7H8"}],["path",{d:"M3.486 21h10"}],["path",{d:"M5 21V8.732"}],["circle",{cx:"6",cy:"7",r:"2"}]],uy=[["path",{d:"M11 17h2"}],["path",{d:"M12 12h.01"}],["path",{d:"M17 12a5 5 0 00-10 0"}],["path",{d:"M19 2v2.8"}],["path",{d:"M2 5h2.8"}],["path",{d:"M22 5h-2.8"}],["path",{d:"M5 2v2.8"}],["circle",{cx:"12",cy:"12",r:"10"}]],gy=[["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"}],["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09"}],["path",{d:"M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05"}]],Ay=[["path",{d:"m15 13 3.708 7.416"}],["path",{d:"M3 19a15 15 0 0 0 18 0"}],["path",{d:"m3 2 3.21 9.633A2 2 0 0 0 8.109 13H18"}],["path",{d:"m9 13-3.708 7.416"}]],Hy=[["path",{d:"M6 19V5"}],["path",{d:"M10 19V6.8"}],["path",{d:"M14 19v-7.8"}],["path",{d:"M18 5v4"}],["path",{d:"M18 19v-6"}],["path",{d:"M22 19V9"}],["path",{d:"M2 19V9a4 4 0 0 1 4-4c2 0 4 1.33 6 4s4 4 6 4a4 4 0 1 0-3-6.65"}]],Vy=[["path",{d:"M17 10h-1a4 4 0 1 1 4-4v.534"}],["path",{d:"M17 6h1a4 4 0 0 1 1.42 7.74l-2.29.87a6 6 0 0 1-5.339-10.68l2.069-1.31"}],["path",{d:"M4.5 17c2.8-.5 4.4 0 5.5.8s1.8 2.2 2.3 3.7c-2 .4-3.5.4-4.8-.3-1.2-.6-2.3-1.9-3-4.2"}],["path",{d:"M9.77 12C4 15 2 22 2 22"}],["circle",{cx:"17",cy:"8",r:"2"}]],l0=[["path",{d:"m15.194 13.707 3.814 1.86-1.86 3.814"}],["path",{d:"M16.47214 7.52786 A 5 10 0 1 0 13 21.79796"}],["path",{d:"M21.79796 11 A 10 5 0 1 0 19 15.57071"}]],i0=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"}],["path",{d:"M3 3v5h5"}],["path",{d:"M12 7v5l4 2"}]],Sy=[["path",{d:"M12 7v6"}],["path",{d:"M12 9h2"}],["path",{d:"M3 12a9 9 0 1 0 9-9 9.74 9.74 0 0 0-6.74 2.74L3 8"}],["path",{d:"M3 3v5h5"}],["circle",{cx:"12",cy:"15",r:"2"}]],wy=[["path",{d:"M20 9V7a2 2 0 0 0-2-2h-6"}],["path",{d:"m15 2-3 3 3 3"}],["path",{d:"M20 13v5a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2"}]],Ly=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"}],["path",{d:"M3 3v5h5"}]],fy=[["path",{d:"M12 7v5l4 2"}],["path",{d:"M16 8h5V3"}],["path",{d:"m21 8-2.3-2.3A9.7 9.7 0 0012 3a9 9 0 109 9"}]],ky=[["path",{d:"M12 3a9.75 9.75 0 0 1 6.74 2.74"}],["path",{d:"M18.74 5.74 21 8"}],["path",{d:"M21 8V3"}],["path",{d:"M7.5 19.794c-6-3.464-6-12.124 0-15.588"}],["path",{d:"M7.5 4.206A9 9 0 0 1 12 3"}],["path",{d:"M12 7v5l4 2"}],["path",{d:"M14 20.775A9 9 0 0 1 12 21"}],["path",{d:"M19 17.656a9 9 0 0 1-1.5 1.456"}],["path",{d:"M21 12a9 9 0 0 1-.228 2"}],["path",{d:"M21 8h-5"}]],Py=[["path",{d:"M12 5H6a2 2 0 0 0-2 2v3"}],["path",{d:"m9 8 3-3-3-3"}],["path",{d:"M4 14v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"}]],By=[["path",{d:"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"}],["path",{d:"M21 3v5h-5"}]],zy=[["circle",{cx:"6",cy:"19",r:"3"}],["path",{d:"M9 19h8.5c.4 0 .9-.1 1.3-.2"}],["path",{d:"M5.2 5.2A3.5 3.53 0 0 0 6.5 12H12"}],["path",{d:"m2 2 20 20"}],["path",{d:"M21 15.3a3.5 3.5 0 0 0-3.3-3.3"}],["path",{d:"M15 5h-4.3"}],["circle",{cx:"18",cy:"5",r:"3"}]],Dy=[["circle",{cx:"6",cy:"19",r:"3"}],["path",{d:"M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15"}],["circle",{cx:"18",cy:"5",r:"3"}]],Fy=[["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2"}],["path",{d:"M6.01 18H6"}],["path",{d:"M10.01 18H10"}],["path",{d:"M15 10v4"}],["path",{d:"M17.84 7.17a4 4 0 0 0-5.66 0"}],["path",{d:"M20.66 4.34a8 8 0 0 0-11.31 0"}]],n0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 12h18"}]],e0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M21 9H3"}],["path",{d:"M21 15H3"}]],Ry=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M21 7.5H3"}],["path",{d:"M21 12H3"}],["path",{d:"M21 16.5H3"}]],by=[["path",{d:"M4 11a9 9 0 0 1 9 9"}],["path",{d:"M4 4a16 16 0 0 1 16 16"}],["circle",{cx:"5",cy:"19",r:"1"}]],Ty=[["path",{d:"m10 10 4 4"}],["path",{d:"m13 7 4 4"}],["path",{d:"M15.34 2.138A15 15 0 002.138 15.34c-.357 2.94.004 4.919.805 5.717.798.8 2.778 1.162 5.718.805A15 15 0 0021.862 8.661c.357-2.94-.004-4.92-.805-5.718-.798-.8-2.778-1.162-5.717-.805"}],["path",{d:"M17 7 7 17"}],["path",{d:"m7 13 4 4"}]],qy=[["path",{d:"M10 15v-3"}],["path",{d:"M14 15v-3"}],["path",{d:"M18 15v-3"}],["path",{d:"M2 8V4"}],["path",{d:"M22 6H2"}],["path",{d:"M22 8V4"}],["path",{d:"M6 15v-3"}],["rect",{x:"2",y:"12",width:"20",height:"8",rx:"2"}]],Uy=[["path",{d:"M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z"}],["path",{d:"m14.5 12.5 2-2"}],["path",{d:"m11.5 9.5 2-2"}],["path",{d:"m8.5 6.5 2-2"}],["path",{d:"m17.5 15.5 2-2"}]],Oy=[["path",{d:"M6 11h8a4 4 0 0 0 0-8H9v18"}],["path",{d:"M6 15h8"}]],Gy=[["path",{d:"M19.496 12a2.5 2.5 0 00.399-2.214A2 2 0 0020.32 6.5 2 2 0 0019 3a2 2 0 00-1.5.68 2 2 0 00-3.287.424 2.5 2.5 0 00-3.189 2.06A3 3 0 0012 12l4-4"}],["path",{d:"M4 12a1 1 0 00-.99 1.133A9 9 0 0012 21a9 9 0 008.99-7.867A1 1 0 0020 12z"}],["path",{d:"M7 21h10"}],["path",{d:"M9.85 6.907A3.5 3.5 0 005.05 12"}]],Zy=[["path",{d:"m2.37 11.223 8.372-6.777a2 2 0 0 1 2.516 0l8.371 6.777"}],["path",{d:"M21 15a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-5.25"}],["path",{d:"M3 15a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h9"}],["path",{d:"m6.67 15 6.13 4.6a2 2 0 0 0 2.8-.4l3.15-4.2"}],["rect",{width:"20",height:"4",x:"2",y:"11",rx:"1"}]],Ey=[["path",{d:"M10 2v15"}],["path",{d:"M7 22a4 4 0 0 1-4-4 1 1 0 0 1 1-1h16a1 1 0 0 1 1 1 4 4 0 0 1-4 4z"}],["path",{d:"M9.159 2.46a1 1 0 0 1 1.521-.193l9.977 8.98A1 1 0 0 1 20 13H4a1 1 0 0 1-.824-1.567z"}]],Wy=[["path",{d:"M18 12a6 6 0 00-6-6"}],["path",{d:"M2.824 10.459a8 8 0 0010.717 10.717c.558-.276.623-1.012.183-1.452l-9.448-9.448c-.44-.44-1.176-.375-1.452.183"}],["path",{d:"M22 12A10 10 0 0012 2"}],["path",{d:"m9 15 4-4"}]],Iy=[["path",{d:"m13.5 6.5-3.148-3.148a1.205 1.205 0 0 0-1.704 0L6.352 5.648a1.205 1.205 0 0 0 0 1.704L9.5 10.5"}],["path",{d:"M16.5 7.5 19 5"}],["path",{d:"m17.5 10.5 3.148 3.148a1.205 1.205 0 0 1 0 1.704l-2.296 2.296a1.205 1.205 0 0 1-1.704 0L13.5 14.5"}],["path",{d:"M9 21a6 6 0 0 0-6-6"}],["path",{d:"M9.352 10.648a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l4.296-4.296a1.205 1.205 0 0 0 0-1.704l-2.296-2.296a1.205 1.205 0 0 0-1.704 0z"}]],Xy=[["path",{d:"m20 19.5-5.5 1.2"}],["path",{d:"M14.5 4v11.22a1 1 0 0 0 1.242.97L20 15.2"}],["path",{d:"m2.978 19.351 5.549-1.363A2 2 0 0 0 10 16V2"}],["path",{d:"M20 10 4 13.5"}]],Ny=[["path",{d:"M10 2v3a1 1 0 0 0 1 1h5"}],["path",{d:"M18 18v-6a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6"}],["path",{d:"M18 22H4a2 2 0 0 1-2-2V6"}],["path",{d:"M8 18a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9.172a2 2 0 0 1 1.414.586l2.828 2.828A2 2 0 0 1 22 6.828V16a2 2 0 0 1-2.01 2z"}]],Ky=[["path",{d:"M12.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h10.2a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4v4.35"}],["path",{d:"m16 19 2 2 4-4"}],["path",{d:"M17 15.13V14a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7"}]],Jy=[["path",{d:"M13 13H8a1 1 0 0 0-1 1v7"}],["path",{d:"M14 8h1"}],["path",{d:"M17 21v-4"}],["path",{d:"m2 2 20 20"}],["path",{d:"M20.41 20.41A2 2 0 0 1 19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 .59-1.41"}],["path",{d:"M9 3h6.2a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V15"}]],Qy=[["path",{d:"M13.33 13H8a1 1 0 00-1 1v7"}],["path",{d:"M14.363 17.634a2 2 0 00-.506.854l-.837 2.87a.5.5 0 00.62.62l2.87-.837a2 2 0 00.854-.506l4.013-4.009a1 1 0 10-3.004-3.004z"}],["path",{d:"M7 3v4a1 1 0 001 1h7"}],["path",{d:"M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h10.2a2 2 0 011.4.6l3.8 3.8a2 2 0 01.6 1.4v.3"}]],jy=[["path",{d:"M12.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h10.2a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V12"}],["path",{d:"M16 13H8a1 1 0 0 0-1 1v7"}],["path",{d:"M19 22v-6"}],["path",{d:"M22 19h-6"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7"}]],r0=[["path",{d:"M5 7v11a1 1 0 0 0 1 1h11"}],["path",{d:"M5.293 18.707 11 13"}],["circle",{cx:"19",cy:"19",r:"2"}],["circle",{cx:"5",cy:"5",r:"2"}]],Yy=[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7"}]],_y=[["path",{d:"M12 3v18"}],["path",{d:"m19 8 3 8a5 5 0 0 1-6 0zV7"}],["path",{d:"M3 7h1a17 17 0 0 0 8-2 17 17 0 0 0 8 2h1"}],["path",{d:"m5 8 3 8a5 5 0 0 1-6 0zV7"}],["path",{d:"M7 21h10"}]],xy=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["path",{d:"M8 7v10"}],["path",{d:"M12 7v10"}],["path",{d:"M17 7v10"}]],as=[["path",{d:"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"}],["path",{d:"M14 15H9v-5"}],["path",{d:"M16 3h5v5"}],["path",{d:"M21 3 9 15"}]],hs=[["path",{d:"M12 12v5.5"}],["path",{d:"M17 3h2a2 2 0 012 2v2"}],["path",{d:"M21 17v2a2 2 0 01-2 2h-2"}],["path",{d:"M3 7V5a2 2 0 012-2h2"}],["path",{d:"M7 21H5a2 2 0 01-2-2v-2"}],["path",{d:"M7.264 9.252 12 12l4.737-2.748"}],["path",{d:"M7.995 8.514A2 2 0 007 10.244v3.516a2 2 0 00.996 1.73l3 1.74a2 2 0 002.008 0l3-1.74A2 2 0 0017 13.76v-3.517a2 2 0 00-.995-1.73l-3-1.742a2 2 0 00-1.892-.064z"}]],ts=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["circle",{cx:"12",cy:"12",r:"1"}],["path",{d:"M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0"}]],ds=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["path",{d:"M8 14s1.5 2 4 2 4-2 4-2"}],["path",{d:"M9 9h.01"}],["path",{d:"M15 9h.01"}]],cs=[["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["path",{d:"M7.828 13.07A3 3 0 0 1 12 8.764a3 3 0 0 1 4.172 4.306l-3.447 3.62a1 1 0 0 1-1.449 0z"}]],Ms=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["path",{d:"M7 12h10"}]],ps=[["path",{d:"M17 12v4a1 1 0 0 1-1 1h-4"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M17 8V7"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M7 17h.01"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["rect",{x:"7",y:"7",width:"5",height:"5",rx:"1"}]],ls=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["circle",{cx:"12",cy:"12",r:"3"}],["path",{d:"m16 16-1.9-1.9"}]],is=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["rect",{width:"8",height:"8",x:"8",y:"8",rx:"1"}]],ns=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}],["path",{d:"M7 8h8"}],["path",{d:"M7 12h10"}],["path",{d:"M7 16h6"}]],es=[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2"}]],rs=[["path",{d:"M14 21v-3a2 2 0 0 0-4 0v3"}],["path",{d:"M18 4.933V21"}],["path",{d:"m4 6 7.106-3.79a2 2 0 0 1 1.788 0L20 6"}],["path",{d:"m6 11-3.52 2.147a1 1 0 0 0-.48.854V19a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5a1 1 0 0 0-.48-.853L18 11"}],["path",{d:"M6 4.933V21"}],["circle",{cx:"12",cy:"9",r:"2"}]],os=[["path",{d:"M5.42 9.42 8 12"}],["circle",{cx:"4",cy:"8",r:"2"}],["path",{d:"m14 6-8.58 8.58"}],["circle",{cx:"4",cy:"16",r:"2"}],["path",{d:"M10.8 14.8 14 18"}],["path",{d:"M16 12h-2"}],["path",{d:"M22 12h-2"}]],vs=[["circle",{cx:"6",cy:"6",r:"3"}],["path",{d:"M8.12 8.12 12 12"}],["path",{d:"M20 4 8.12 15.88"}],["circle",{cx:"6",cy:"18",r:"3"}],["path",{d:"M14.8 14.8 20 20"}]],$s=[["path",{d:"M21 4h-3.5l2 11.05"}],["path",{d:"M6.95 17h5.142c.523 0 .95-.406 1.063-.916a6.5 6.5 0 0 1 5.345-5.009"}],["circle",{cx:"19.5",cy:"17.5",r:"2.5"}],["circle",{cx:"4.5",cy:"17.5",r:"2.5"}]],ms=[["path",{d:"M19 5a2 2 0 012 2v11a2 2 0 01-2 2h-5.586a1 1 0 00-.707.293l-1.414 1.414a1 1 0 01-.707.293H8.414a1 1 0 01-.707-.293l-1.414-1.414A1 1 0 005.586 20H5a2 2 0 01-2-2V5.286c0-.394.11-.785.36-1.09a6 6 0 019.168-.133C12.996 4.6 13.637 5 14.35 5z"}],["path",{d:"M21 12h-7.586a1 1 0 00-.707.293l-1.414 1.414a1 1 0 01-.707.293H8.414a1 1 0 01-.707-.293l-1.414-1.414A1 1 0 005.586 12H3"}]],ys=[["path",{d:"M13 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-3"}],["path",{d:"M8 21h8"}],["path",{d:"M12 17v4"}],["path",{d:"m22 3-5 5"}],["path",{d:"m17 3 5 5"}]],ss=[["path",{d:"M13 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-3"}],["path",{d:"M8 21h8"}],["path",{d:"M12 17v4"}],["path",{d:"m17 8 5-5"}],["path",{d:"M17 3h5v5"}]],Cs=[["path",{d:"M10 9v10"}],["path",{d:"M14 13v2"}],["path",{d:"M16.5 10.5 5.706 21.294A2.4 2.4 0 014.002 22H2.5a.5.5 0 01-.5-.5v-1.502a2.4 2.4 0 01.706-1.704L13.5 7.5"}],["path",{d:"M20.819 11.575a6 6 0 00.969-5.162.6.6 0 00-.981-.219l-.455.454a1.2 1.2 0 01-1.704 0l-1.296-1.296a1.2 1.2 0 010-1.704l.454-.453a.595.595 0 00-.219-.98 6 6 0 00-5.162.967 2 2 0 00-.222 3.019l5.596 5.597a2 2 0 003.02-.222"}],["path",{d:"M6 13v8"}]],us=[["path",{d:"M19 17V5a2 2 0 0 0-2-2H4"}],["path",{d:"M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"}]],gs=[["path",{d:"M15 12h-5"}],["path",{d:"M15 8h-5"}],["path",{d:"M19 17V5a2 2 0 0 0-2-2H4"}],["path",{d:"M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"}]],As=[["circle",{cx:"11",cy:"11",r:"8"}],["path",{d:"m21 21-4.3-4.3"}],["path",{d:"M11 7v4"}],["path",{d:"M11 15h.01"}]],Hs=[["path",{d:"m8 11 2 2 4-4"}],["circle",{cx:"11",cy:"11",r:"8"}],["path",{d:"m21 21-4.3-4.3"}]],Vs=[["path",{d:"m13 13.5 2-2.5-2-2.5"}],["path",{d:"m21 21-4.3-4.3"}],["path",{d:"M9 8.5 7 11l2 2.5"}],["circle",{cx:"11",cy:"11",r:"8"}]],Ss=[["path",{d:"m13.5 8.5-5 5"}],["circle",{cx:"11",cy:"11",r:"8"}],["path",{d:"m21 21-4.3-4.3"}]],ws=[["path",{d:"m13.5 8.5-5 5"}],["path",{d:"m8.5 8.5 5 5"}],["circle",{cx:"11",cy:"11",r:"8"}],["path",{d:"m21 21-4.3-4.3"}]],Ls=[["path",{d:"M16 5a4 3 0 0 0-8 0c0 4 8 3 8 7a4 3 0 0 1-8 0"}],["path",{d:"M8 19a4 3 0 0 0 8 0c0-4-8-3-8-7a4 3 0 0 1 8 0"}]],fs=[["path",{d:"m21 21-4.34-4.34"}],["circle",{cx:"11",cy:"11",r:"8"}]],o0=[["path",{d:"M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z"}],["path",{d:"M6 12h16"}]],ks=[["rect",{x:"14",y:"14",width:"8",height:"8",rx:"2"}],["rect",{x:"2",y:"2",width:"8",height:"8",rx:"2"}],["path",{d:"M7 14v1a2 2 0 0 0 2 2h1"}],["path",{d:"M14 7h1a2 2 0 0 1 2 2v1"}]],Ps=[["path",{d:"M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"}],["path",{d:"m21.854 2.147-10.94 10.939"}]],Bs=[["path",{d:"m16 16-4 4-4-4"}],["path",{d:"M3 12h18"}],["path",{d:"m8 8 4-4 4 4"}]],zs=[["path",{d:"M12 3v18"}],["path",{d:"m16 16 4-4-4-4"}],["path",{d:"m8 8-4 4 4 4"}]],Ds=[["path",{d:"m10.852 14.772-.383.923"}],["path",{d:"M13.148 14.772a3 3 0 1 0-2.296-5.544l-.383-.923"}],["path",{d:"m13.148 9.228.383-.923"}],["path",{d:"m13.53 15.696-.382-.924a3 3 0 1 1-2.296-5.544"}],["path",{d:"m14.772 10.852.923-.383"}],["path",{d:"m14.772 13.148.923.383"}],["path",{d:"M4.5 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-.5"}],["path",{d:"M4.5 14H4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2h-.5"}],["path",{d:"M6 18h.01"}],["path",{d:"M6 6h.01"}],["path",{d:"m9.228 10.852-.923-.383"}],["path",{d:"m9.228 13.148-.923.383"}]],Fs=[["path",{d:"M6 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2"}],["path",{d:"M6 14H4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2h-2"}],["path",{d:"M6 6h.01"}],["path",{d:"M6 18h.01"}],["path",{d:"m13 6-4 6h6l-4 6"}]],Rs=[["path",{d:"M10 16V8"}],["path",{d:"M14 16V8"}],["path",{d:"M18 16V8"}],["path",{d:"M6 12h.01"}],["path",{d:"M6 16h.01"}],["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2"}]],bs=[["path",{d:"M7 2h13a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-5"}],["path",{d:"M10 10 2.5 2.5C2 2 2 2.5 2 5v3a2 2 0 0 0 2 2h6z"}],["path",{d:"M22 17v-1a2 2 0 0 0-2-2h-1"}],["path",{d:"M4 14a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16.5l1-.5.5.5-8-8H4z"}],["path",{d:"M6 18h.01"}],["path",{d:"m2 2 20 20"}]],Ts=[["path",{d:"M12.5 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v2"}],["path",{d:"M16 12h6"}],["path",{d:"M19 9v6"}],["path",{d:"M22 18v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h8.5"}],["path",{d:"M6 18h.01"}],["path",{d:"M6 6h.01"}]],qs=[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18"}]],Us=[["path",{d:"M14 17H5"}],["path",{d:"M19 7h-9"}],["circle",{cx:"17",cy:"17",r:"3"}],["circle",{cx:"7",cy:"7",r:"3"}]],Os=[["path",{d:"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"}],["circle",{cx:"12",cy:"12",r:"3"}]],Gs=[["path",{d:"M8.3 10a.7.7 0 0 1-.626-1.079L11.4 3a.7.7 0 0 1 1.198-.043L16.3 8.9a.7.7 0 0 1-.572 1.1Z"}],["rect",{x:"3",y:"14",width:"7",height:"7",rx:"1"}],["circle",{cx:"17.5",cy:"17.5",r:"3.5"}]],Zs=[["path",{d:"M12 2v13"}],["path",{d:"m16 6-4-4-4 4"}],["path",{d:"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"}]],Es=[["circle",{cx:"18",cy:"5",r:"3"}],["circle",{cx:"6",cy:"12",r:"3"}],["circle",{cx:"18",cy:"19",r:"3"}],["line",{x1:"8.59",x2:"15.42",y1:"13.51",y2:"17.49"}],["line",{x1:"15.41",x2:"8.59",y1:"6.51",y2:"10.49"}]],Ws=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["line",{x1:"3",x2:"21",y1:"9",y2:"9"}],["line",{x1:"3",x2:"21",y1:"15",y2:"15"}],["line",{x1:"9",x2:"9",y1:"9",y2:"21"}],["line",{x1:"15",x2:"15",y1:"9",y2:"21"}]],Is=[["path",{d:"M14 11a2 2 0 1 1-4 0 4 4 0 0 1 8 0 6 6 0 0 1-12 0 8 8 0 0 1 16 0 10 10 0 1 1-20 0 11.93 11.93 0 0 1 2.42-7.22 2 2 0 1 1 3.16 2.44"}]],Xs=[["path",{d:"M12 12V9a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3"}],["path",{d:"M16 20v-3a1 1 0 0 0-1-1h-2a1 1 0 0 0-1 1v3"}],["path",{d:"M20 22V2"}],["path",{d:"M4 12h16"}],["path",{d:"M4 20h16"}],["path",{d:"M4 2v20"}],["path",{d:"M4 4h16"}]],Ns=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"M12 8v4"}],["path",{d:"M12 16h.01"}]],Ks=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"m4.243 5.21 14.39 12.472"}]],Js=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"m9 12 2 2 4-4"}]],Qs=[["path",{d:"M11 22c-3.806-1.45-7-3.966-7-9V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1v4"}],["path",{d:"M14.923 16.547 14 16.164"}],["path",{d:"m14.923 18.843-.923.383"}],["path",{d:"M16.547 14.923 16.164 14"}],["path",{d:"m16.547 20.467-.383.924"}],["path",{d:"m18.843 14.923.383-.923"}],["path",{d:"m19.225 21.391-.382-.924"}],["path",{d:"m20.467 16.547.923-.383"}],["path",{d:"m20.467 18.843.923.383"}],["circle",{cx:"17.695",cy:"17.695",r:"3"}]],js=[["path",{d:"m10.929 14.467-.383.924"}],["path",{d:"M10.929 8.923 10.546 8"}],["path",{d:"M13.225 8.923 13.608 8"}],["path",{d:"m13.607 15.391-.382-.924"}],["path",{d:"m14.849 10.547.923-.383"}],["path",{d:"m14.849 12.843.923.383"}],["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"m9.305 10.547-.923-.383"}],["path",{d:"m9.305 12.843-.923.383"}],["circle",{cx:"12.077",cy:"11.695",r:"3"}]],Ys=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"M8 12h.01"}],["path",{d:"M12 12h.01"}],["path",{d:"M16 12h.01"}]],_s=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"M12 22V2"}]],xs=[["path",{d:"M12 13v3"}],["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 01-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 011-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 011.52 0C14.51 3.81 17 5 19 5a1 1 0 011 1z"}],["circle",{cx:"12",cy:"11",r:"2"}]],aC=[["path",{d:"M15 22a1 1 0 01-1-1v-4a1 1 0 01.445-.832l3-2a1 1 0 011.11 0l3 2A1 1 0 0122 17v4a1 1 0 01-1 1z"}],["path",{d:"M18 22v-3"}],["path",{d:"M20 10.418V6a1 1 0 00-1-1c-2 0-4.49-1.19-6.24-2.72a1.17 1.17 0 00-1.52 0C9.5 3.8 7 5 5 5a1 1 0 00-1 1v7c0 4.602 2.67 7.1 6.037 8.603"}]],hC=[["path",{d:"M20 9.807V6a1 1 0 00-1-1c-2 0-4.49-1.19-6.24-2.72a1.17 1.17 0 00-1.52 0C9.5 3.8 7 5 5 5a1 1 0 00-1 1v7c0 3.88 2.107 6.254 5 7.796"}],["path",{d:"M19 17v-2a2 2 0 00-4 0v2"}],["rect",{x:"13",y:"17",width:"8",height:"5",rx:"1"}]],tC=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"M9 12h6"}]],dC=[["path",{d:"m2 2 20 20"}],["path",{d:"M5 5a1 1 0 0 0-1 1v7c0 5 3.5 7.5 7.67 8.94a1 1 0 0 0 .67.01c2.35-.82 4.48-1.97 5.9-3.71"}],["path",{d:"M9.309 3.652A12.252 12.252 0 0 0 11.24 2.28a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1v7a9.784 9.784 0 0 1-.08 1.264"}]],cC=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"M9 12h6"}],["path",{d:"M12 9v6"}]],v0=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3"}],["path",{d:"M12 17h.01"}]],$0=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"m14.5 9.5-5 5"}],["path",{d:"m9.5 9.5 5 5"}]],MC=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"M6.376 18.91a6 6 0 0 1 11.249.003"}],["circle",{cx:"12",cy:"11",r:"4"}]],pC=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}]],lC=[["path",{d:"M12 15v-3"}],["path",{d:"M12 2v2"}],["path",{d:"M16.5 12V9a1 1 0 011-1h1a1 1 0 001-1V5a1 1 0 00-1-1h-13a1 1 0 00-1 1v2a1 1 0 001 1h1a1 1 0 011 1v3"}],["path",{d:"M19.38 19c1.076-1.815 1.636-4.89 1.628-6.008a1 1 0 00-1-.992H3.984a1 1 0 00-1 .984c-.03 1.86.97 5.621 2.826 7.776"}],["path",{d:"M2 20c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1s1.2 1 2.5 1c2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"}]],iC=[["circle",{cx:"12",cy:"12",r:"8"}],["path",{d:"M12 2v7.5"}],["path",{d:"m19 5-5.23 5.23"}],["path",{d:"M22 12h-7.5"}],["path",{d:"m19 19-5.23-5.23"}],["path",{d:"M12 14.5V22"}],["path",{d:"M10.23 13.77 5 19"}],["path",{d:"M9.5 12H2"}],["path",{d:"M10.23 10.23 5 5"}],["circle",{cx:"12",cy:"12",r:"2.5"}]],nC=[["path",{d:"M12 2v2"}],["path",{d:"M12 9.189V13"}],["path",{d:"M19 12V6a2 2 0 00-2-2H7a2 2 0 00-2 2v6"}],["path",{d:"M19.38 19A11.6 11.6 0 0021 13l-8.188-3.639a2 2 0 00-1.624 0L3 13.001a11.6 11.6 0 002.81 7.76"}],["path",{d:"M2 20c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1s1.2 1 2.5 1c2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"}]],eC=[["path",{d:"M20.38 3.46 16 2a4 4 0 0 1-8 0L3.62 3.46a2 2 0 0 0-1.34 2.23l.58 3.47a1 1 0 0 0 .99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2V10h2.15a1 1 0 0 0 .99-.84l.58-3.47a2 2 0 0 0-1.34-2.23z"}]],rC=[["path",{d:"M16 10a4 4 0 0 1-8 0"}],["path",{d:"M3.103 6.034h17.794"}],["path",{d:"M3.4 5.467a2 2 0 0 0-.4 1.2V20a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.667a2 2 0 0 0-.4-1.2l-2-2.667A2 2 0 0 0 17 2H7a2 2 0 0 0-1.6.8z"}]],oC=[["path",{d:"M16 5h6"}],["path",{d:"m2.05 2.05 1.099-.028a1 1 0 011.008.815l2.69 14.347A1 1 0 007.83 18H18"}],["path",{d:"M4.564 5H12"}],["path",{d:"M6.25 14h12.712a2 2 0 001.991-1.57l.514-3.113"}],["circle",{cx:"18",cy:"20",r:"2"}],["circle",{cx:"8",cy:"20",r:"2"}]],vC=[["path",{d:"m15 11-1 9"}],["path",{d:"m19 11-4-7"}],["path",{d:"M2 11h20"}],["path",{d:"m3.5 11 1.6 7.4a2 2 0 0 0 2 1.6h9.8a2 2 0 0 0 2-1.6l1.7-7.4"}],["path",{d:"M4.5 15.5h15"}],["path",{d:"m5 11 4-7"}],["path",{d:"m9 11 1 9"}]],$C=[["path",{d:"M16 5h6"}],["path",{d:"M19 2v6"}],["path",{d:"m2.05 2.05 1.099-.028a1 1 0 011.008.815l2.69 14.347A1 1 0 007.83 18H18"}],["path",{d:"M4.564 5H12"}],["path",{d:"M6.25 14h12.712a2 2 0 001.991-1.57l.172-1.041"}],["circle",{cx:"18",cy:"20",r:"2"}],["circle",{cx:"8",cy:"20",r:"2"}]],mC=[["path",{d:"m2.05 2.05 1.099-.028a1 1 0 0 1 1.008.815l2.69 14.347A1 1 0 0 0 7.83 18H18"}],["path",{d:"M4.563 5h16.435a1 1 0 0 1 .981 1.204l-1.026 6.226A2 2 0 0 1 18.962 14H6.25"}],["circle",{cx:"18",cy:"20",r:"2"}],["circle",{cx:"8",cy:"20",r:"2"}]],yC=[["path",{d:"M21.56 4.56a1.5 1.5 0 0 1 0 2.122l-.47.47a3 3 0 0 1-4.212-.03 3 3 0 0 1 0-4.243l.44-.44a1.5 1.5 0 0 1 2.121 0z"}],["path",{d:"M3 22a1 1 0 0 1-1-1v-3.586a1 1 0 0 1 .293-.707l3.355-3.355a1.205 1.205 0 0 1 1.704 0l3.296 3.296a1.205 1.205 0 0 1 0 1.704l-3.355 3.355a1 1 0 0 1-.707.293z"}],["path",{d:"m9 15 7.879-7.878"}]],sC=[["path",{d:"m4 4 2.5 2.5"}],["path",{d:"M13.5 6.5a4.95 4.95 0 0 0-7 7"}],["path",{d:"M15 5 5 15"}],["path",{d:"M14 17v.01"}],["path",{d:"M10 16v.01"}],["path",{d:"M13 13v.01"}],["path",{d:"M16 10v.01"}],["path",{d:"M11 20v.01"}],["path",{d:"M17 14v.01"}],["path",{d:"M20 11v.01"}]],CC=[["path",{d:"M4 13V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v5"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{d:"M10 22v-5"}],["path",{d:"M14 19v-2"}],["path",{d:"M18 20v-3"}],["path",{d:"M2 13h20"}],["path",{d:"M6 20v-3"}]],uC=[["path",{d:"M10 2a3.28 3.28 0 003.227 1.798l6.17-.561A1 1 0 1119.614 8H13.5"}],["path",{d:"M11 20c-.5.5-1.12 1-2.5 1a1 1 0 010-5H12a7 7 0 003.283-.817"}],["path",{d:"M11 22c-.5-.5-1.12-1-2.5-1a6.5 6.5 0 01-5.63-3.25 6.44 6.44 0 015.236-9.744"}],["path",{d:"M18.04 12.54A7 7 0 0019 9V8"}],["path",{d:"m2 2 20 20"}],["path",{d:"M8 16c-2 0-4.5-4-4-6"}],["path",{d:"M9.43 9.33A8.5 8.5 0 0010 16"}]],gC=[["path",{d:"M10 2a3.28 3.28 0 003.227 1.798l6.17-.561A1 1 0 1119.614 8H8.5a6.44 6.44 0 00-5.63 9.75A6.5 6.5 0 008.5 21c1.38 0 2-.5 2.5-1"}],["path",{d:"M10 8a8.5 8.5 0 000 8"}],["path",{d:"M11 22c-.5-.5-1.12-1-2.5-1a1 1 0 010-5H12a7 7 0 007-7V8"}],["path",{d:"M13 12h.01"}],["path",{d:"M8 16c-2 0-4.5-4-4-6"}]],AC=[["path",{d:"m15 15 6 6m-6-6v4.8m0-4.8h4.8"}],["path",{d:"M9 19.8V15m0 0H4.2M9 15l-6 6"}],["path",{d:"M15 4.2V9m0 0h4.8M15 9l6-6"}],["path",{d:"M9 4.2V9m0 0H4.2M9 9 3 3"}]],HC=[["path",{d:"M12 22v-5.172a2 2 0 0 0-.586-1.414L9.5 13.5"}],["path",{d:"M14.5 14.5 12 17"}],["path",{d:"M17 8.8A6 6 0 0 1 13.8 20H10A6.5 6.5 0 0 1 7 8a5 5 0 0 1 10 0z"}]],VC=[["path",{d:"m18 14 4 4-4 4"}],["path",{d:"m18 2 4 4-4 4"}],["path",{d:"M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22"}],["path",{d:"M2 6h1.972a4 4 0 0 1 3.6 2.2"}],["path",{d:"M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45"}]],SC=[["path",{d:"M18 7V5a1 1 0 0 0-1-1H6.5a.5.5 0 0 0-.4.8l4.5 6a2 2 0 0 1 0 2.4l-4.5 6a.5.5 0 0 0 .4.8H17a1 1 0 0 0 1-1v-2"}]],wC=[["path",{d:"M2 20h.01"}],["path",{d:"M7 20v-4"}],["path",{d:"M12 20v-8"}],["path",{d:"M17 20V8"}]],LC=[["path",{d:"M2 20h.01"}],["path",{d:"M7 20v-4"}]],fC=[["path",{d:"M2 20h.01"}],["path",{d:"M7 20v-4"}],["path",{d:"M12 20v-8"}]],kC=[["path",{d:"M2 20h.01"}]],PC=[["path",{d:"M2 20h.01"}],["path",{d:"M7 20v-4"}],["path",{d:"M12 20v-8"}],["path",{d:"M17 20V8"}],["path",{d:"M22 4v16"}]],BC=[["path",{d:"m21 17-2.156-1.868A.5.5 0 0 0 18 15.5v.5a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1c0-2.545-3.991-3.97-8.5-4a1 1 0 0 0 0 5c4.153 0 4.745-11.295 5.708-13.5a2.5 2.5 0 1 1 3.31 3.284"}],["path",{d:"M3 21h18"}]],zC=[["path",{d:"M10 9H4L2 7l2-2h6"}],["path",{d:"M14 5h6l2 2-2 2h-6"}],["path",{d:"M10 22V4a2 2 0 1 1 4 0v18"}],["path",{d:"M8 22h8"}]],DC=[["path",{d:"M12 13v8"}],["path",{d:"M12 3v3"}],["path",{d:"M2.354 10.354a1.207 1.207 0 0 1 0-1.708l2.06-2.06A2 2 0 0 1 5.828 6h12.344a2 2 0 0 1 1.414.586l2.06 2.06a1.207 1.207 0 0 1 0 1.708l-2.06 2.06a2 2 0 0 1-1.414.586H5.828a2 2 0 0 1-1.414-.586z"}]],FC=[["path",{d:"M7 18v-6a5 5 0 1 1 10 0v6"}],["path",{d:"M5 21a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2z"}],["path",{d:"M21 12h1"}],["path",{d:"M18.5 4.5 18 5"}],["path",{d:"M2 12h1"}],["path",{d:"M12 2v1"}],["path",{d:"m4.929 4.929.707.707"}],["path",{d:"M12 12v6"}]],m0=[["path",{d:"M21 16V3a1 1 0 0 0-1.28-.96l-10 2.917a1 1 0 0 0-.72.96V19"}],["path",{d:"M21 6 9 9.5"}],["circle",{cx:"18",cy:"16",r:"3"}],["circle",{cx:"6",cy:"19",r:"3"}]],RC=[["path",{d:"M17.971 4.285A2 2 0 0 1 21 6v12a2 2 0 0 1-3.029 1.715l-9.997-5.998a2 2 0 0 1-.003-3.432z"}],["path",{d:"M3 20V4"}]],bC=[["path",{d:"M21 4v16"}],["path",{d:"M6.029 4.285A2 2 0 0 0 3 6v12a2 2 0 0 0 3.029 1.715l9.997-5.998a2 2 0 0 0 .003-3.432z"}]],TC=[["path",{d:"m12.5 17-.5-1-.5 1h1z"}],["path",{d:"M15 22a1 1 0 0 0 1-1v-1a2 2 0 0 0 1.56-3.25 8 8 0 1 0-11.12 0A2 2 0 0 0 8 20v1a1 1 0 0 0 1 1z"}],["circle",{cx:"15",cy:"12",r:"1"}],["circle",{cx:"9",cy:"12",r:"1"}]],qC=[["path",{d:"M22 2 2 22"}]],UC=[["path",{d:"M11 16.586V19a1 1 0 0 1-1 1H2L18.37 3.63a1 1 0 1 1 3 3l-9.663 9.663a1 1 0 0 1-1.414 0L8 14"}]],OC=[["path",{d:"M10 5H3"}],["path",{d:"M12 19H3"}],["path",{d:"M14 3v4"}],["path",{d:"M16 17v4"}],["path",{d:"M21 12h-9"}],["path",{d:"M21 19h-5"}],["path",{d:"M21 5h-7"}],["path",{d:"M8 10v4"}],["path",{d:"M8 12H3"}]],y0=[["path",{d:"M10 8h4"}],["path",{d:"M12 21v-9"}],["path",{d:"M12 8V3"}],["path",{d:"M17 16h4"}],["path",{d:"M19 12V3"}],["path",{d:"M19 21v-5"}],["path",{d:"M3 14h4"}],["path",{d:"M5 10V3"}],["path",{d:"M5 21v-7"}]],GC=[["rect",{width:"14",height:"20",x:"5",y:"2",rx:"2",ry:"2"}],["path",{d:"M12.667 8 10 12h4l-2.667 4"}]],ZC=[["rect",{width:"7",height:"12",x:"2",y:"6",rx:"1"}],["path",{d:"M13 8.32a7.43 7.43 0 0 1 0 7.36"}],["path",{d:"M16.46 6.21a11.76 11.76 0 0 1 0 11.58"}],["path",{d:"M19.91 4.1a15.91 15.91 0 0 1 .01 15.8"}]],EC=[["path",{d:"M10 22H6a2 2 0 01-2-2V4a2 2 0 012-2h10a2 2 0 012 2v4"}],["path",{d:"M11 18h.01"}],["path",{d:"M22 22a4 4 0 00-8 0"}],["circle",{cx:"18",cy:"15",r:"3"}]],WC=[["rect",{width:"14",height:"20",x:"5",y:"2",rx:"2",ry:"2"}],["path",{d:"M12 18h.01"}]],IC=[["path",{d:"M2 13a6 6 0 1 0 12 0 4 4 0 1 0-8 0 2 2 0 0 0 4 0"}],["circle",{cx:"10",cy:"13",r:"8"}],["path",{d:"M2 21h12c4.4 0 8-3.6 8-8V7a2 2 0 1 0-4 0v6"}],["path",{d:"M18 3 19.1 5.2"}],["path",{d:"M22 3 20.9 5.2"}]],XC=[["path",{d:"m10 20-1.25-2.5L6 18"}],["path",{d:"M10 4 8.75 6.5 6 6"}],["path",{d:"m14 20 1.25-2.5L18 18"}],["path",{d:"m14 4 1.25 2.5L18 6"}],["path",{d:"m17 21-3-6h-4"}],["path",{d:"m17 3-3 6 1.5 3"}],["path",{d:"M2 12h6.5L10 9"}],["path",{d:"m20 10-1.5 2 1.5 2"}],["path",{d:"M22 12h-6.5L14 15"}],["path",{d:"m4 10 1.5 2L4 14"}],["path",{d:"m7 21 3-6-1.5-3"}],["path",{d:"m7 3 3 6h4"}]],NC=[["path",{d:"M10.5 2v4"}],["path",{d:"M14 2H7a2 2 0 0 0-2 2"}],["path",{d:"M19.29 14.76A6.67 6.67 0 0 1 17 11a6.6 6.6 0 0 1-2.29 3.76c-1.15.92-1.71 2.04-1.71 3.19 0 2.22 1.8 4.05 4 4.05s4-1.83 4-4.05c0-1.16-.57-2.26-1.71-3.19"}],["path",{d:"M9.607 21H6a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h7V7a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3"}]],KC=[["path",{d:"M20 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v3"}],["path",{d:"M2 16a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z"}],["path",{d:"M4 18v2"}],["path",{d:"M20 18v2"}],["path",{d:"M12 4v9"}]],JC=[["path",{d:"M11 2h2"}],["path",{d:"m14.28 14-4.56 8"}],["path",{d:"m21 22-1.558-4H4.558"}],["path",{d:"M3 10v2"}],["path",{d:"M6.245 15.04A2 2 0 0 1 8 14h12a1 1 0 0 1 .864 1.505l-3.11 5.457A2 2 0 0 1 16 22H4a1 1 0 0 1-.863-1.506z"}],["path",{d:"M7 2a4 4 0 0 1-4 4"}],["path",{d:"m8.66 7.66 1.41 1.41"}]],QC=[["path",{d:"M11.248 3c.273.1.808.53.747 1.36-.05.83-.938 1.2-.989 2.02-.06.78.333 1.24.727 1.62"}],["path",{d:"M16.252 3c.268.1.794.53.745 1.36-.06.83-.923 1.2-.993 2.02-.05.78.338 1.24.725 1.62"}],["path",{d:"M19.5 12 22 6"}],["path",{d:"M4 12a1 1 0 00-.99 1.133A9 9 0 0012 21a9 9 0 008.99-7.867A1 1 0 0020 12z"}],["path",{d:"M6.252 3c.268.1.794.53.745 1.36-.06.83-.923 1.2-.993 2.02-.05.78.338 1.24.735 1.62"}],["path",{d:"M7 21h10"}]],jC=[["path",{d:"M22 17v1c0 .5-.5 1-1 1H3c-.5 0-1-.5-1-1v-1"}]],YC=[["path",{d:"M12 18v4"}],["path",{d:"M2 14.499a5.5 5.5 0 0 0 9.591 3.675.6.6 0 0 1 .818.001A5.5 5.5 0 0 0 22 14.5c0-2.29-1.5-4-3-5.5l-5.492-5.312a2 2 0 0 0-3-.02L5 8.999c-1.5 1.5-3 3.2-3 5.5"}]],_C=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"}]],s0=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"}],["path",{d:"M20 2v4"}],["path",{d:"M22 4h-4"}],["circle",{cx:"4",cy:"20",r:"2"}]],xC=[["rect",{width:"16",height:"20",x:"4",y:"2",rx:"2"}],["path",{d:"M12 6h.01"}],["circle",{cx:"12",cy:"14",r:"4"}],["path",{d:"M12 14h.01"}]],au=[["path",{d:"m6 16 6-12 6 12"}],["path",{d:"M8 12h8"}],["path",{d:"M4 21c1.1 0 1.1-1 2.3-1s1.1 1 2.3 1c1.1 0 1.1-1 2.3-1 1.1 0 1.1 1 2.3 1 1.1 0 1.1-1 2.3-1 1.1 0 1.1 1 2.3 1 1.1 0 1.1-1 2.3-1"}]],hu=[["path",{d:"M8.8 20v-4.1l1.9.2a2.3 2.3 0 0 0 2.164-2.1V8.3A5.37 5.37 0 0 0 2 8.25c0 2.8.656 3.054 1 4.55a5.77 5.77 0 0 1 .029 2.758L2 20"}],["path",{d:"M19.8 17.8a7.5 7.5 0 0 0 .003-10.603"}],["path",{d:"M17 15a3.5 3.5 0 0 0-.025-4.975"}]],tu=[["path",{d:"m20 15-5.5 5.5L12 18"}],["path",{d:"m4 16 6-12 5.115 10.23"}],["path",{d:"M6 12h8"}]],du=[["path",{d:"M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"}],["path",{d:"M5 17A12 12 0 0 1 17 5"}],["circle",{cx:"19",cy:"5",r:"2"}],["circle",{cx:"5",cy:"19",r:"2"}]],cu=[["circle",{cx:"19",cy:"5",r:"2"}],["circle",{cx:"5",cy:"19",r:"2"}],["path",{d:"M5 17A12 12 0 0 1 17 5"}]],Mu=[["path",{d:"M16 3h5v5"}],["path",{d:"M8 3H3v5"}],["path",{d:"M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3"}],["path",{d:"m15 9 6-6"}]],pu=[["path",{d:"M17 13.44 4.442 17.082A2 2 0 0 0 4.982 21H19a2 2 0 0 0 .558-3.921l-1.115-.32A2 2 0 0 1 17 14.837V7.66"}],["path",{d:"m7 10.56 12.558-3.642A2 2 0 0 0 19.018 3H5a2 2 0 0 0-.558 3.921l1.115.32A2 2 0 0 1 7 9.163v7.178"}]],lu=[["path",{d:"m15 10.42 4.8-5.07"}],["path",{d:"M19 18h3"}],["path",{d:"M9.5 22 21.414 9.415A2 2 0 0 0 21.2 6.4l-5.61-4.208A1 1 0 0 0 14 3v2a2 2 0 0 1-1.394 1.906L8.677 8.053A1 1 0 0 0 8 9c-.155 6.393-2.082 9-4 9a2 2 0 0 0 0 4h14"}]],iu=[["path",{d:"M15.295 19.562 16 22"}],["path",{d:"m17 16 3.758 2.098"}],["path",{d:"m19 12.5 3.026-.598"}],["path",{d:"M7.61 6.3a3 3 0 0 0-3.92 1.3l-1.38 2.79a3 3 0 0 0 1.3 3.91l6.89 3.597a1 1 0 0 0 1.342-.447l3.106-6.211a1 1 0 0 0-.447-1.341z"}],["path",{d:"M8 9V2"}]],nu=[["path",{d:"M3 3h.01"}],["path",{d:"M7 5h.01"}],["path",{d:"M11 7h.01"}],["path",{d:"M3 7h.01"}],["path",{d:"M7 9h.01"}],["path",{d:"M3 11h.01"}],["rect",{width:"4",height:"4",x:"15",y:"5"}],["path",{d:"m19 9 2 2v10c0 .6-.4 1-1 1h-6c-.6 0-1-.4-1-1V11l2-2"}],["path",{d:"m13 14 8-2"}],["path",{d:"m13 19 8-2"}]],eu=[["path",{d:"M14 9.536V7a4 4 0 0 1 4-4h1.5a.5.5 0 0 1 .5.5V5a4 4 0 0 1-4 4 4 4 0 0 0-4 4c0 2 1 3 1 5a5 5 0 0 1-1 3"}],["path",{d:"M4 9a5 5 0 0 1 8 4 5 5 0 0 1-8-4"}],["path",{d:"M5 21h14"}]],C0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M17 12h-2l-2 5-2-10-2 5H7"}]],u0=[["path",{d:"M15 15H9l6-6"}],["path",{d:"M9 15V9"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],g0=[["path",{d:"M15 15 9 9"}],["path",{d:"M9 15h6V9"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],A0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M12 8v8"}],["path",{d:"m8 12 4 4 4-4"}]],H0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"m12 8-4 4 4 4"}],["path",{d:"M16 12H8"}]],V0=[["path",{d:"M13 21h6a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6"}],["path",{d:"m3 21 9-9"}],["path",{d:"M9 21H3v-6"}]],S0=[["path",{d:"M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6"}],["path",{d:"m21 21-9-9"}],["path",{d:"M21 15v6h-6"}]],w0=[["path",{d:"M13 3h6a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-6"}],["path",{d:"m3 3 9 9"}],["path",{d:"M3 9V3h6"}]],L0=[["path",{d:"M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"}],["path",{d:"m21 3-9 9"}],["path",{d:"M15 3h6v6"}]],ru=[["path",{d:"m10 16 4-4-4-4"}],["path",{d:"M3 12h11"}],["path",{d:"M3 8V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3"}]],ou=[["path",{d:"M10 12h11"}],["path",{d:"m17 16 4-4-4-4"}],["path",{d:"M21 6.344V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-1.344"}]],f0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M8 12h8"}],["path",{d:"m12 16 4-4-4-4"}]],k0=[["path",{d:"M15 15 9 9"}],["path",{d:"M9 15V9h6"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],P0=[["path",{d:"M15 15V9H9"}],["path",{d:"m9 15 6-6"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],B0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"m16 12-4-4-4 4"}],["path",{d:"M12 16V8"}]],z0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M12 8v8"}],["path",{d:"m8.5 14 7-4"}],["path",{d:"m8.5 10 7 4"}]],D0=[["path",{d:"M14 21h1"}],["path",{d:"m17 17-2.18-2.18"}],["path",{d:"M5 21a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v14a2 2 0 01-2 2"}],["path",{d:"M9 21h1"}],["path",{d:"M9.56 14.44 17 7"}],["path",{d:"M9.56 9.56 12 12"}],["circle",{cx:"8.5",cy:"15.5",r:"1.5"}],["circle",{cx:"8.5",cy:"8.5",r:"1.5"}]],F0=[["path",{d:"M8 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h3"}],["path",{d:"M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3"}],["path",{d:"M12 20v2"}],["path",{d:"M12 14v2"}],["path",{d:"M12 8v2"}],["path",{d:"M12 2v2"}]],R0=[["path",{d:"M21 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v3"}],["path",{d:"M21 16v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3"}],["path",{d:"M4 12H2"}],["path",{d:"M10 12H8"}],["path",{d:"M16 12h-2"}],["path",{d:"M22 12h-2"}]],m=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M9 8h7"}],["path",{d:"M8 12h6"}],["path",{d:"M11 16h5"}]],b0=[["path",{d:"M11 3v7.751a.25.25 0 00.407.195l2.28-1.834a.5.5 0 01.627 0l2.28 1.834a.25.25 0 00.406-.195V3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],T0=[["path",{d:"M21 10.656V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h12.344"}],["path",{d:"m9 11 3 3L22 4"}]],q0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"m16 9-5.5 5.5L8 12"}]],U0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"m10 8 4 4-4 4"}]],O0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"m16 10-4 4-4-4"}]],G0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"m8 14 4-4 4 4"}]],Z0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"m14 16-4-4 4-4"}]],E0=[["path",{d:"m10 9-3 3 3 3"}],["path",{d:"m14 15 3-3-3-3"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],vu=[["path",{d:"M10 9.5 8 12l2 2.5"}],["path",{d:"M14 21h1"}],["path",{d:"m14 9.5 2 2.5-2 2.5"}],["path",{d:"M5 21a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2"}],["path",{d:"M9 21h1"}]],$u=[["path",{d:"M5 21a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2"}],["path",{d:"M9 21h1"}],["path",{d:"M14 21h1"}]],W0=[["path",{d:"M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"}],["path",{d:"M5 3a2 2 0 0 0-2 2"}],["path",{d:"M19 3a2 2 0 0 1 2 2"}],["path",{d:"M5 21a2 2 0 0 1-2-2"}],["path",{d:"M9 3h1"}],["path",{d:"M9 21h2"}],["path",{d:"M14 3h1"}],["path",{d:"M3 9v1"}],["path",{d:"M21 9v2"}],["path",{d:"M3 14v1"}]],I0=[["path",{d:"M8 7v7"}],["path",{d:"M12 7v4"}],["path",{d:"M16 7v9"}],["path",{d:"M5 3a2 2 0 0 0-2 2"}],["path",{d:"M9 3h1"}],["path",{d:"M14 3h1"}],["path",{d:"M19 3a2 2 0 0 1 2 2"}],["path",{d:"M21 9v1"}],["path",{d:"M21 14v1"}],["path",{d:"M21 19a2 2 0 0 1-2 2"}],["path",{d:"M14 21h1"}],["path",{d:"M9 21h1"}],["path",{d:"M5 21a2 2 0 0 1-2-2"}],["path",{d:"M3 14v1"}],["path",{d:"M3 9v1"}]],mu=[["path",{d:"M5 3a2 2 0 0 0-2 2"}],["path",{d:"M19 3a2 2 0 0 1 2 2"}],["path",{d:"M21 19a2 2 0 0 1-2 2"}],["path",{d:"M5 21a2 2 0 0 1-2-2"}],["path",{d:"M9 3h1"}],["path",{d:"M9 21h1"}],["path",{d:"M14 3h1"}],["path",{d:"M14 21h1"}],["path",{d:"M3 9v1"}],["path",{d:"M21 9v1"}],["path",{d:"M3 14v1"}],["path",{d:"M21 14v1"}],["path",{d:"M8 12h8"}],["path",{d:"M12 8v8"}]],y=[["path",{d:"M14 21h1"}],["path",{d:"M14 3h1"}],["path",{d:"M19 3a2 2 0 0 1 2 2"}],["path",{d:"M21 14v1"}],["path",{d:"M21 19a2 2 0 0 1-2 2"}],["path",{d:"M21 9v1"}],["path",{d:"M3 14v1"}],["path",{d:"M3 9v1"}],["path",{d:"M5 21a2 2 0 0 1-2-2"}],["path",{d:"M5 3a2 2 0 0 0-2 2"}],["path",{d:"M7 12h10"}],["path",{d:"M7 16h6"}],["path",{d:"M7 8h8"}],["path",{d:"M9 21h1"}],["path",{d:"M9 3h1"}]],yu=[["path",{d:"M14 21h1"}],["path",{d:"M21 14v1"}],["path",{d:"M21 19a2 2 0 0 1-2 2"}],["path",{d:"M21 9v1"}],["path",{d:"M3 14v1"}],["path",{d:"M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2"}],["path",{d:"M3 9v1"}],["path",{d:"M5 21a2 2 0 0 1-2-2"}],["path",{d:"M9 21h1"}]],su=[["path",{d:"M14 3h1"}],["path",{d:"m16 16 5 5"}],["path",{d:"M19 3a2 2 0 012 2"}],["path",{d:"m21 16-5 5"}],["path",{d:"M21 9v1"}],["path",{d:"M3 14v1"}],["path",{d:"M3 9v1"}],["path",{d:"M5 21a2 2 0 01-2-2"}],["path",{d:"M5 3a2 2 0 00-2 2"}],["path",{d:"M9 21h1"}],["path",{d:"M9 3h1"}]],Cu=[["path",{d:"M14 21h1"}],["path",{d:"M14 3h1"}],["path",{d:"M19 3a2 2 0 012 2"}],["path",{d:"M21 14v1"}],["path",{d:"M21 19a2 2 0 01-2 2"}],["path",{d:"M21 9v1"}],["path",{d:"M3 14v1"}],["path",{d:"M3 9v1"}],["path",{d:"M5 21a2 2 0 01-2-2"}],["path",{d:"M5 3a2 2 0 00-2 2"}],["path",{d:"m9 15 6-6"}],["path",{d:"M9 21h1"}],["path",{d:"M9 3h1"}],["path",{d:"m9 9 6 6"}]],X0=[["path",{d:"M5 3a2 2 0 0 0-2 2"}],["path",{d:"M19 3a2 2 0 0 1 2 2"}],["path",{d:"M21 19a2 2 0 0 1-2 2"}],["path",{d:"M5 21a2 2 0 0 1-2-2"}],["path",{d:"M9 3h1"}],["path",{d:"M9 21h1"}],["path",{d:"M14 3h1"}],["path",{d:"M14 21h1"}],["path",{d:"M3 9v1"}],["path",{d:"M21 9v1"}],["path",{d:"M3 14v1"}],["path",{d:"M21 14v1"}]],uu=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M12 7H7v5"}],["path",{d:"M12 17h5v-5"}]],N0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["line",{x1:"8",x2:"16",y1:"12",y2:"12"}],["line",{x1:"12",x2:"12",y1:"16",y2:"16"}],["line",{x1:"12",x2:"12",y1:"8",y2:"8"}]],K0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M7 10h10"}],["path",{d:"M7 14h10"}]],gu=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16"}]],J0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["circle",{cx:"12",cy:"12",r:"1"}]],Q0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["path",{d:"M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3"}],["path",{d:"M9 11.2h5.7"}]],j0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M8 7v7"}],["path",{d:"M12 7v4"}],["path",{d:"M16 7v9"}]],Y0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M7 7v10"}],["path",{d:"M11 7v10"}],["path",{d:"m15 7 2 10"}]],_0=[["path",{d:"M8 16V8.5a.5.5 0 0 1 .9-.3l2.7 3.599a.5.5 0 0 0 .8 0l2.7-3.6a.5.5 0 0 1 .9.3V16"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],x0=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M7 8h10"}],["path",{d:"M7 12h10"}],["path",{d:"M7 16h10"}]],aa=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M8 12h8"}]],ha=[["path",{d:"M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"}],["path",{d:"M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6"}]],Au=[["path",{d:"M20.4 20.4a2 2 0 01-1.4.6H5a2 2 0 01-2-2V5a2 2 0 01.59-1.41"}],["path",{d:"M21 15.3V5a2 2 0 00-2-2H8.7"}],["path",{d:"M22 22 2 2"}]],ta=[["path",{d:"M3.6 3.6A2 2 0 0 1 5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-.59 1.41"}],["path",{d:"M3 8.7V19a2 2 0 0 0 2 2h10.3"}],["path",{d:"m2 2 20 20"}],["path",{d:"M13 13a3 3 0 1 0 0-6H9v2"}],["path",{d:"M9 17v-2.3"}]],da=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M9 17V7h4a3 3 0 0 1 0 6H9"}]],Hu=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["line",{x1:"10",x2:"10",y1:"15",y2:"9"}],["line",{x1:"14",x2:"14",y1:"15",y2:"9"}]],l=[["path",{d:"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"}],["path",{d:"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"}]],ca=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"m15 9-6 6"}],["path",{d:"M9 9h.01"}],["path",{d:"M15 15h.01"}]],Ma=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M7 7h10"}],["path",{d:"M10 7v10"}],["path",{d:"M16 17a2 2 0 0 1-2-2V7"}]],pa=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M12 12H9.5a2.5 2.5 0 0 1 0-5H17"}],["path",{d:"M12 7v10"}],["path",{d:"M16 7v10"}]],la=[["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["path",{d:"M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z"}]],ia=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M8 12h8"}],["path",{d:"M12 8v8"}]],Vu=[["path",{d:"M7 12h2l2 5 2-10h4"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],na=[["path",{d:"M12 7v4"}],["path",{d:"M7.998 9.003a5 5 0 1 0 8-.005"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],Su=[["path",{d:"M21 11a8 8 0 0 0-8-8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"}]],ea=[["path",{d:"m17 17-2.18-2.18"}],["path",{d:"M9.56 14.44 17 7"}],["path",{d:"M9.56 9.56 12 12"}],["circle",{cx:"8.5",cy:"15.5",r:"1.5"}],["circle",{cx:"8.5",cy:"8.5",r:"1.5"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],ra=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M16 8.9V7H8l4 5-4 5h8v-1.9"}]],wu=[["path",{d:"M11 15H7"}],["path",{d:"M15.41 2.49a.6.6 0 011.18 0l.63 3.334a1.2 1.2 0 00.956.955l3.334.631a.6.6 0 010 1.18l-3.334.63a1.2 1.2 0 00-.955.956l-.631 3.334a.6.6 0 01-1.18 0l-.63-3.334a1.2 1.2 0 00-.956-.955L10.49 8.59a.6.6 0 010-1.18l3.334-.63a1.2 1.2 0 00.955-.956z"}],["path",{d:"M21 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h6"}],["path",{d:"M9 13v4"}]],oa=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["line",{x1:"9",x2:"15",y1:"15",y2:"9"}]],va=[["path",{d:"M12 2v20"}],["path",{d:"M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3"}],["path",{d:"M8 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3"}]],$a=[["path",{d:"M2 12h20"}],["path",{d:"M21 16v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3"}],["path",{d:"M3 8V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v3"}]],Lu=[["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}],["rect",{x:"8",y:"8",width:"8",height:"8",rx:"1"}]],fu=[["path",{d:"M4 10c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2"}],["path",{d:"M10 16c-1.1 0-2-.9-2-2v-4c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2"}],["rect",{width:"8",height:"8",x:"14",y:"14",rx:"2"}]],ku=[["path",{d:"M11.035 7.69a1 1 0 0 1 1.909.024l.737 1.452a1 1 0 0 0 .737.535l1.634.256a1 1 0 0 1 .588 1.806l-1.172 1.168a1 1 0 0 0-.282.866l.259 1.613a1 1 0 0 1-1.541 1.134l-1.465-.75a1 1 0 0 0-.912 0l-1.465.75a1 1 0 0 1-1.539-1.133l.258-1.613a1 1 0 0 0-.282-.866l-1.156-1.153a1 1 0 0 1 .572-1.822l1.633-.256a1 1 0 0 0 .737-.535z"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],Pu=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["rect",{x:"9",y:"9",width:"6",height:"6",rx:"1"}]],Bu=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M7 8h8"}],["path",{d:"M7 12h10"}],["path",{d:"M7 16h6"}]],ma=[["path",{d:"M18 21a6 6 0 0 0-12 0"}],["circle",{cx:"12",cy:"11",r:"4"}],["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}]],ya=[["path",{d:"m7 11 2-2-2-2"}],["path",{d:"M11 13h4"}],["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}]],sa=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["circle",{cx:"12",cy:"10",r:"3"}],["path",{d:"M7 21v-2a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v2"}]],Ca=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2"}],["path",{d:"m15 9-6 6"}],["path",{d:"m9 9 6 6"}]],zu=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}]],Du=[["path",{d:"M16 12v2a2 2 0 0 1-2 2H9a1 1 0 0 0-1 1v3a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2h0"}],["path",{d:"M4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v3a1 1 0 0 1-1 1h-5a2 2 0 0 0-2 2v2"}]],Fu=[["path",{d:"M10 22a2 2 0 0 1-2-2"}],["path",{d:"M14 2a2 2 0 0 1 2 2"}],["path",{d:"M16 22h-2"}],["path",{d:"M2 10V8"}],["path",{d:"M2 4a2 2 0 0 1 2-2"}],["path",{d:"M20 8a2 2 0 0 1 2 2"}],["path",{d:"M22 14v2"}],["path",{d:"M22 20a2 2 0 0 1-2 2"}],["path",{d:"M4 16a2 2 0 0 1-2-2"}],["path",{d:"M8 10a2 2 0 0 1 2-2h5a1 1 0 0 1 1 1v5a2 2 0 0 1-2 2H9a1 1 0 0 1-1-1z"}],["path",{d:"M8 2h2"}]],Ru=[["path",{d:"M10 22a2 2 0 0 1-2-2"}],["path",{d:"M16 22h-2"}],["path",{d:"M16 4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-5a2 2 0 0 1 2-2h5a1 1 0 0 0 1-1z"}],["path",{d:"M20 8a2 2 0 0 1 2 2"}],["path",{d:"M22 14v2"}],["path",{d:"M22 20a2 2 0 0 1-2 2"}]],bu=[["path",{d:"M4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v3a1 1 0 0 0 1 1h3a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-3a1 1 0 0 0-1-1z"}]],Tu=[["path",{d:"M13.77 3.043a34 34 0 0 0-3.54 0"}],["path",{d:"M13.771 20.956a33 33 0 0 1-3.541.001"}],["path",{d:"M20.18 17.74c-.51 1.15-1.29 1.93-2.439 2.44"}],["path",{d:"M20.18 6.259c-.51-1.148-1.291-1.929-2.44-2.438"}],["path",{d:"M20.957 10.23a33 33 0 0 1 0 3.54"}],["path",{d:"M3.043 10.23a34 34 0 0 0 .001 3.541"}],["path",{d:"M6.26 20.179c-1.15-.508-1.93-1.29-2.44-2.438"}],["path",{d:"M6.26 3.82c-1.149.51-1.93 1.291-2.44 2.44"}]],qu=[["path",{d:"M15.236 22a3 3 0 0 0-2.2-5"}],["path",{d:"M16 20a3 3 0 0 1 3-3h1a2 2 0 0 0 2-2v-2a4 4 0 0 0-4-4V4"}],["path",{d:"M18 13h.01"}],["path",{d:"M18 6a4 4 0 0 0-4 4 7 7 0 0 0-7 7c0-5 4-5 4-10.5a4.5 4.5 0 1 0-9 0 2.5 2.5 0 0 0 5 0C7 10 3 11 3 17c0 2.8 2.2 5 5 5h10"}]],Uu=[["path",{d:"M12 3c7.2 0 9 1.8 9 9s-1.8 9-9 9-9-1.8-9-9 1.8-9 9-9"}]],Ou=[["path",{d:"M14 13V8.5C14 7 15 7 15 5a3 3 0 0 0-6 0c0 2 1 2 1 3.5V13"}],["path",{d:"M20 15.5a2.5 2.5 0 0 0-2.5-2.5h-11A2.5 2.5 0 0 0 4 15.5V17a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1z"}],["path",{d:"M5 22h14"}]],Gu=[["path",{d:"m19.06 12.501 2.78-2.707a.53.53 0 0 0-.294-.905l-5.166-.755a2.1 2.1 0 0 1-1.595-1.16l-2.31-4.68a.53.53 0 0 0-.95.001L9.216 6.974a2.1 2.1 0 0 1-1.597 1.16l-5.165.755a.53.53 0 0 0-.294.906l3.736 3.637a2.1 2.1 0 0 1 .611 1.879l-.88 5.139a.53.53 0 0 0 .769.56l4.617-2.428.027-.014"}],["path",{d:"m15 18 2 2 4-4"}]],Zu=[["path",{d:"M12 18.338a2.1 2.1 0 0 0-.987.244L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16l2.309-4.679A.53.53 0 0 1 12 2"}]],Eu=[["path",{d:"M15 18h6"}],["path",{d:"M17.688 14a2.1 2.1 0 0 1 .416-.568l3.736-3.638a.53.53 0 0 0-.294-.905l-5.166-.755a2.1 2.1 0 0 1-1.595-1.16l-2.31-4.68a.53.53 0 0 0-.95.001L9.216 6.974a2.1 2.1 0 0 1-1.597 1.16l-5.165.755a.53.53 0 0 0-.294.906l3.736 3.637a2.1 2.1 0 0 1 .611 1.879l-.88 5.139a.53.53 0 0 0 .769.56l4.617-2.428.027-.014"}]],Wu=[["path",{d:"m10.344 4.688 1.181-2.393a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.237 3.152"}],["path",{d:"m17.945 17.945.43 2.505a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a8 8 0 0 0 .4-.099"}],["path",{d:"m2 2 20 20"}]],Iu=[["path",{d:"M11.013 18.582 6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16l2.309-4.679a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904L20 11.5"}],["path",{d:"M15 18h6"}],["path",{d:"M18 15v6"}]],Xu=[["path",{d:"m15.5 15.5 5 5"}],["path",{d:"m20.063 11.525 1.777-1.731a.53.53 0 0 0-.294-.905l-5.166-.755a2.1 2.1 0 0 1-1.595-1.16l-2.31-4.68a.53.53 0 0 0-.95.001L9.216 6.974a2.1 2.1 0 0 1-1.597 1.16l-5.165.755a.53.53 0 0 0-.294.906l3.736 3.637a2.1 2.1 0 0 1 .611 1.879l-.88 5.139a.53.53 0 0 0 .769.56l4.617-2.428a2.1 2.1 0 0 1 .987-.243 2 2 0 0 1 .132.004"}],["path",{d:"m20.5 15.5-5 5"}]],Nu=[["path",{d:"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"}]],Ku=[["path",{d:"M13.971 4.285A2 2 0 0 1 17 6v12a2 2 0 0 1-3.029 1.715l-9.997-5.998a2 2 0 0 1-.003-3.432z"}],["path",{d:"M21 20V4"}]],Ju=[["path",{d:"M10.029 4.285A2 2 0 0 0 7 6v12a2 2 0 0 0 3.029 1.715l9.997-5.998a2 2 0 0 0 .003-3.432z"}],["path",{d:"M3 4v16"}]],Qu=[["path",{d:"M11 2v2"}],["path",{d:"M5 2v2"}],["path",{d:"M5 3H4a2 2 0 0 0-2 2v4a6 6 0 0 0 12 0V5a2 2 0 0 0-2-2h-1"}],["path",{d:"M8 15a6 6 0 0 0 12 0v-3"}],["circle",{cx:"20",cy:"10",r:"2"}]],ju=[["path",{d:"M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z"}],["path",{d:"M15 3v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 13h.01"}],["path",{d:"M16 13h.01"}],["path",{d:"M10 16s.8 1 2 1c1.3 0 2-1 2-1"}]],Yu=[["path",{d:"m15 19 2 2 4-4"}],["path",{d:"M15 3v5a1 1 0 0 0 1 1h5"}],["path",{d:"M21 13V9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6.5"}]],_u=[["path",{d:"M15 3v5a1 1 0 0 0 1 1h5"}],["path",{d:"M21 14V9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7.35"}],["path",{d:"M21 18h-6"}]],xu=[["path",{d:"M15 3v5a1 1 0 0 0 1 1h5"}],["path",{d:"m2 2 20 20"}],["path",{d:"M3.586 3.586A2 2 0 0 0 3 5v14a2 2 0 0 0 2 2h14a2 2 0 0 0 1.414-.586"}],["path",{d:"M8.656 3H15a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 21 9v6.344"}]],ag=[["path",{d:"M15 3v5a1 1 0 0 0 1 1h5"}],["path",{d:"M18 15v6"}],["path",{d:"M21 12.356V9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7.355"}],["path",{d:"M21 18h-6"}]],hg=[["path",{d:"M15 3v5a1 1 0 0 0 1 1h5"}],["path",{d:"m16 16 5 5"}],["path",{d:"M21 12V9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7"}],["path",{d:"m21 16-5 5"}]],tg=[["path",{d:"M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z"}],["path",{d:"M15 3v5a1 1 0 0 0 1 1h5"}]],dg=[["path",{d:"M10 8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 16 14v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2z"}],["path",{d:"M10 8v5a1 1 0 0 0 1 1h5"}],["path",{d:"M8 4a2 2 0 0 1 2-2h6a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 22 8v6a2 2 0 0 1-2 2"}],["path",{d:"M16 2v5a1 1 0 0 0 1 1h5"}]],cg=[["path",{d:"M11.264 2.205A4 4 0 0 0 6.42 4.211l-4 8a4 4 0 0 0 1.359 5.117l6 4a4 4 0 0 0 4.438 0l6-4a4 4 0 0 0 1.576-4.592l-2-6a4 4 0 0 0-2.53-2.53z"}],["path",{d:"M11.99 22 14 12l7.822 3.184"}],["path",{d:"M14 12 8.47 2.302"}]],Mg=[["path",{d:"M15 21v-5a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v5"}],["path",{d:"M17.774 10.31a1.12 1.12 0 0 0-1.549 0 2.5 2.5 0 0 1-3.451 0 1.12 1.12 0 0 0-1.548 0 2.5 2.5 0 0 1-3.452 0 1.12 1.12 0 0 0-1.549 0 2.5 2.5 0 0 1-3.77-3.248l2.889-4.184A2 2 0 0 1 7 2h10a2 2 0 0 1 1.653.873l2.895 4.192a2.5 2.5 0 0 1-3.774 3.244"}],["path",{d:"M4 10.95V19a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8.05"}]],pg=[["rect",{width:"20",height:"6",x:"2",y:"4",rx:"2"}],["rect",{width:"20",height:"6",x:"2",y:"14",rx:"2"}]],lg=[["rect",{width:"6",height:"20",x:"4",y:"2",rx:"2"}],["rect",{width:"6",height:"20",x:"14",y:"2",rx:"2"}]],ig=[["path",{d:"M16 4H9a3 3 0 0 0-2.83 4"}],["path",{d:"M14 12a4 4 0 0 1 0 8H6"}],["line",{x1:"4",x2:"20",y1:"12",y2:"12"}]],ng=[["path",{d:"m4 5 8 8"}],["path",{d:"m12 5-8 8"}],["path",{d:"M20 19h-4c0-1.5.44-2 1.5-2.5S20 15.33 20 14c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07"}]],eg=[["path",{d:"M15 4H7"}],["path",{d:"m18 16 3 3-3 3"}],["path",{d:"M3 4v13a2 2 0 0 0 2 2h16"}],["path",{d:"M7 14h7"}],["path",{d:"M7 9h12"}]],rg=[["circle",{cx:"12",cy:"12",r:"4"}],["path",{d:"M12 4h.01"}],["path",{d:"M20 12h.01"}],["path",{d:"M12 20h.01"}],["path",{d:"M4 12h.01"}],["path",{d:"M17.657 6.343h.01"}],["path",{d:"M17.657 17.657h.01"}],["path",{d:"M6.343 17.657h.01"}],["path",{d:"M6.343 6.343h.01"}]],og=[["circle",{cx:"12",cy:"12",r:"4"}],["path",{d:"M12 3v1"}],["path",{d:"M12 20v1"}],["path",{d:"M3 12h1"}],["path",{d:"M20 12h1"}],["path",{d:"m18.364 5.636-.707.707"}],["path",{d:"m6.343 17.657-.707.707"}],["path",{d:"m5.636 5.636.707.707"}],["path",{d:"m17.657 17.657.707.707"}]],vg=[["path",{d:"M12 2v2"}],["path",{d:"M14.837 16.385a6 6 0 1 1-7.223-7.222c.624-.147.97.66.715 1.248a4 4 0 0 0 5.26 5.259c.589-.255 1.396.09 1.248.715"}],["path",{d:"M16 12a4 4 0 0 0-4-4"}],["path",{d:"m19 5-1.256 1.256"}],["path",{d:"M20 12h2"}]],$g=[["path",{d:"M10 21v-1"}],["path",{d:"M10 4V3"}],["path",{d:"M10 9a3 3 0 0 0 0 6"}],["path",{d:"m14 20 1.25-2.5L18 18"}],["path",{d:"m14 4 1.25 2.5L18 6"}],["path",{d:"m17 21-3-6 1.5-3H22"}],["path",{d:"m17 3-3 6 1.5 3"}],["path",{d:"M2 12h1"}],["path",{d:"m20 10-1.5 2 1.5 2"}],["path",{d:"m3.64 18.36.7-.7"}],["path",{d:"m4.34 6.34-.7-.7"}]],mg=[["circle",{cx:"12",cy:"12",r:"4"}],["path",{d:"M12 2v2"}],["path",{d:"M12 20v2"}],["path",{d:"m4.93 4.93 1.41 1.41"}],["path",{d:"m17.66 17.66 1.41 1.41"}],["path",{d:"M2 12h2"}],["path",{d:"M20 12h2"}],["path",{d:"m6.34 17.66-1.41 1.41"}],["path",{d:"m19.07 4.93-1.41 1.41"}]],yg=[["path",{d:"M12 2v8"}],["path",{d:"m4.93 10.93 1.41 1.41"}],["path",{d:"M2 18h2"}],["path",{d:"M20 18h2"}],["path",{d:"m19.07 10.93-1.41 1.41"}],["path",{d:"M22 22H2"}],["path",{d:"m8 6 4-4 4 4"}],["path",{d:"M16 18a4 4 0 0 0-8 0"}]],sg=[["path",{d:"M12 10V2"}],["path",{d:"m4.93 10.93 1.41 1.41"}],["path",{d:"M2 18h2"}],["path",{d:"M20 18h2"}],["path",{d:"m19.07 10.93-1.41 1.41"}],["path",{d:"M22 22H2"}],["path",{d:"m16 6-4 4-4-4"}],["path",{d:"M16 18a4 4 0 0 0-8 0"}]],Cg=[["path",{d:"m4 19 8-8"}],["path",{d:"m12 19-8-8"}],["path",{d:"M20 12h-4c0-1.5.442-2 1.5-2.5S20 8.334 20 7.002c0-.472-.17-.93-.484-1.29a2.105 2.105 0 0 0-2.617-.436c-.42.239-.738.614-.899 1.06"}]],ug=[["path",{d:"M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z"}],["path",{d:"M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7"}],["path",{d:"M 7 17h.01"}],["path",{d:"m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8"}]],gg=[["path",{d:"M10 21V3h8"}],["path",{d:"M6 16h9"}],["path",{d:"M10 9.5h7"}]],Ag=[["path",{d:"M11 19H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5"}],["path",{d:"M13 5h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-5"}],["circle",{cx:"12",cy:"12",r:"3"}],["path",{d:"m18 22-3-3 3-3"}],["path",{d:"m6 2 3 3-3 3"}]],Hg=[["path",{d:"m11 19-6-6"}],["path",{d:"m5 21-2-2"}],["path",{d:"m8 16-4 4"}],["path",{d:"M9.5 17.5 20.414 6.586A2 2 0 0021 5.172V3h-2.172a2 2 0 00-1.414.586L6.5 14.5"}]],Vg=[["path",{d:"m13 19 6-6"}],["path",{d:"M14.5 17.5 3.586 6.586A2 2 0 013 5.172V3h2.172a2 2 0 011.414.586L17.5 14.5"}],["path",{d:"m14.828 6.172 2.586-2.586A2 2 0 0118.828 3H21v2.172a2 2 0 01-.586 1.414l-2.586 2.586"}],["path",{d:"m16 16 4 4"}],["path",{d:"m19 21 2-2"}],["path",{d:"m5 14 4 4"}],["path",{d:"m5 21-2-2"}],["path",{d:"M7.5 16.5 4 20"}]],Sg=[["path",{d:"m18 2 4 4"}],["path",{d:"m17 7 3-3"}],["path",{d:"M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5"}],["path",{d:"m9 11 4 4"}],["path",{d:"m5 19-3 3"}],["path",{d:"m14 4 6 6"}]],wg=[["path",{d:"M3 9h18"}],["path",{d:"M9 3v18"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],Lg=[["path",{d:"M12 21v-6"}],["path",{d:"M12 9V3"}],["path",{d:"M3 15h18"}],["path",{d:"M3 9h18"}],["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}]],fg=[["path",{d:"M12 15V9"}],["path",{d:"M3 15h18"}],["path",{d:"M3 9h18"}],["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}]],kg=[["path",{d:"M14 14v2"}],["path",{d:"M14 20v2"}],["path",{d:"M14 2v2"}],["path",{d:"M14 8v2"}],["path",{d:"M2 15h8"}],["path",{d:"M2 3h6a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H2"}],["path",{d:"M2 9h8"}],["path",{d:"M22 15h-4"}],["path",{d:"M22 3h-2a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2"}],["path",{d:"M22 9h-4"}],["path",{d:"M5 3v18"}]],Pg=[["path",{d:"M16 5H3"}],["path",{d:"M16 12H3"}],["path",{d:"M16 19H3"}],["path",{d:"M21 5h.01"}],["path",{d:"M21 12h.01"}],["path",{d:"M21 19h.01"}]],Bg=[["path",{d:"M15 3v18"}],["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M21 9H3"}],["path",{d:"M21 15H3"}]],zg=[["path",{d:"M12 3v18"}],["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["path",{d:"M3 9h18"}],["path",{d:"M3 15h18"}]],Dg=[["path",{d:"M14 10h2"}],["path",{d:"M15 22v-8"}],["path",{d:"M15 2v4"}],["path",{d:"M2 10h2"}],["path",{d:"M20 10h2"}],["path",{d:"M3 19h18"}],["path",{d:"M3 22v-6a2 2 135 0 1 2-2h14a2 2 45 0 1 2 2v6"}],["path",{d:"M3 2v2a2 2 45 0 0 2 2h14a2 2 135 0 0 2-2V2"}],["path",{d:"M8 10h2"}],["path",{d:"M9 22v-8"}],["path",{d:"M9 2v4"}]],Fg=[["rect",{width:"10",height:"14",x:"3",y:"8",rx:"2"}],["path",{d:"M5 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2h-2.4"}],["path",{d:"M8 18h.01"}]],Rg=[["rect",{width:"16",height:"20",x:"4",y:"2",rx:"2",ry:"2"}],["line",{x1:"12",x2:"12.01",y1:"18",y2:"18"}]],bg=[["path",{d:"M16 13h6"}],["path",{d:"m16.5 6.5-3.914-3.914A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l1.79-1.79"}],["path",{d:"M19 10v6"}],["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor"}]],Tg=[["circle",{cx:"7",cy:"7",r:"5"}],["circle",{cx:"17",cy:"17",r:"5"}],["path",{d:"M12 17h10"}],["path",{d:"m3.46 10.54 7.08-7.08"}]],qg=[["path",{d:"m16.5 6.5-3.914-3.914A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.43 2.43 0 0 0 3.42 0l1.79-1.79"}],["path",{d:"m16.5 10.5 5 5"}],["path",{d:"m21.5 10.5-5 5"}],["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor"}]],Ug=[["path",{d:"M13.172 2a2 2 0 0 1 1.414.586l6.71 6.71a2.4 2.4 0 0 1 0 3.408l-4.592 4.592a2.4 2.4 0 0 1-3.408 0l-6.71-6.71A2 2 0 0 1 6 9.172V3a1 1 0 0 1 1-1z"}],["path",{d:"M2 7v6.172a2 2 0 0 0 .586 1.414l6.71 6.71a2.4 2.4 0 0 0 3.191.193"}],["circle",{cx:"10.5",cy:"6.5",r:".5",fill:"currentColor"}]],Og=[["path",{d:"M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"}],["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor"}]],Gg=[["path",{d:"M4 4v16"}]],Zg=[["path",{d:"M4 4v16"}],["path",{d:"M9 4v16"}]],Eg=[["path",{d:"M4 4v16"}],["path",{d:"M9 4v16"}],["path",{d:"M14 4v16"}]],Wg=[["path",{d:"M4 4v16"}],["path",{d:"M9 4v16"}],["path",{d:"M14 4v16"}],["path",{d:"M19 4v16"}],["path",{d:"M22 6 2 18"}]],Ig=[["path",{d:"M4 4v16"}],["path",{d:"M9 4v16"}],["path",{d:"M14 4v16"}],["path",{d:"M19 4v16"}]],Xg=[["circle",{cx:"17",cy:"4",r:"2"}],["path",{d:"M15.59 5.41 5.41 15.59"}],["circle",{cx:"4",cy:"17",r:"2"}],["path",{d:"M12 22s-4-9-1.5-11.5S22 12 22 12"}]],Ng=[["circle",{cx:"12",cy:"12",r:"10"}],["circle",{cx:"12",cy:"12",r:"6"}],["circle",{cx:"12",cy:"12",r:"2"}]],Kg=[["path",{d:"m10.065 12.493-6.18 1.318a.934.934 0 0 1-1.108-.702l-.537-2.15a1.07 1.07 0 0 1 .691-1.265l13.504-4.44"}],["path",{d:"m13.56 11.747 4.332-.924"}],["path",{d:"m16 21-3.105-6.21"}],["path",{d:"M16.485 5.94a2 2 0 0 1 1.455-2.425l1.09-.272a1 1 0 0 1 1.212.727l1.515 6.06a1 1 0 0 1-.727 1.213l-1.09.272a2 2 0 0 1-2.425-1.455z"}],["path",{d:"m6.158 8.633 1.114 4.456"}],["path",{d:"m8 21 3.105-6.21"}],["circle",{cx:"12",cy:"13",r:"2"}]],Jg=[["circle",{cx:"4",cy:"4",r:"2"}],["path",{d:"m14 5 3-3 3 3"}],["path",{d:"m14 10 3-3 3 3"}],["path",{d:"M17 14V2"}],["path",{d:"M17 14H7l-5 8h20Z"}],["path",{d:"M8 14v8"}],["path",{d:"m9 14 5 8"}]],Qg=[["path",{d:"M12 19h8"}],["path",{d:"m4 17 6-6-6-6"}]],jg=[["path",{d:"M3.5 21 14 3"}],["path",{d:"M20.5 21 10 3"}],["path",{d:"M15.5 21 12 15l-3.5 6"}],["path",{d:"M2 21h20"}]],ua=[["path",{d:"M21 7 6.82 21.18a2.83 2.83 0 0 1-3.99-.01a2.83 2.83 0 0 1 0-4L17 3"}],["path",{d:"m16 2 6 6"}],["path",{d:"M12 16H4"}]],Yg=[["path",{d:"M14.5 2v17.5c0 1.4-1.1 2.5-2.5 2.5c-1.4 0-2.5-1.1-2.5-2.5V2"}],["path",{d:"M8.5 2h7"}],["path",{d:"M14.5 16h-5"}]],_g=[["path",{d:"M9 2v17.5A2.5 2.5 0 0 1 6.5 22A2.5 2.5 0 0 1 4 19.5V2"}],["path",{d:"M20 2v17.5a2.5 2.5 0 0 1-2.5 2.5a2.5 2.5 0 0 1-2.5-2.5V2"}],["path",{d:"M3 2h7"}],["path",{d:"M14 2h7"}],["path",{d:"M9 16H4"}],["path",{d:"M20 16h-5"}]],ga=[["path",{d:"M21 5H3"}],["path",{d:"M17 12H7"}],["path",{d:"M19 19H5"}]],Aa=[["path",{d:"M21 5H3"}],["path",{d:"M21 12H9"}],["path",{d:"M21 19H7"}]],xg=[["path",{d:"M3 5h18"}],["path",{d:"M3 12h18"}],["path",{d:"M7 19h10"}]],aA=[["path",{d:"M3 5h18"}],["path",{d:"M3 12h18"}],["path",{d:"M11 19h10"}]],hA=[["path",{d:"M3 5h18"}],["path",{d:"M3 12h18"}],["path",{d:"M3 19h10"}]],Ha=[["path",{d:"M3 5h18"}],["path",{d:"M3 12h18"}],["path",{d:"M3 19h18"}]],tA=[["path",{d:"M12 20h-1a2 2 0 0 1-2-2 2 2 0 0 1-2 2H6"}],["path",{d:"M13 8h7a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-7"}],["path",{d:"M5 16H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h1"}],["path",{d:"M6 4h1a2 2 0 0 1 2 2 2 2 0 0 1 2-2h1"}],["path",{d:"M9 6v12"}]],s=[["path",{d:"M21 5H3"}],["path",{d:"M15 12H3"}],["path",{d:"M17 19H3"}]],dA=[["path",{d:"M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"}],["path",{d:"M7 22h1a4 4 0 0 0 4-4"}],["path",{d:"M7 2h1a4 4 0 0 1 4 4"}]],Va=[["path",{d:"M15 5h6"}],["path",{d:"M15 12h6"}],["path",{d:"M3 19h18"}],["path",{d:"m3 12 3.553-7.724a.5.5 0 0 1 .894 0L11 12"}],["path",{d:"M3.92 10h6.16"}]],cA=[["path",{d:"M17 5H3"}],["path",{d:"M21 12H8"}],["path",{d:"M21 19H8"}],["path",{d:"M3 12v7"}]],MA=[["path",{d:"M21 5H3"}],["path",{d:"M10 12H3"}],["path",{d:"M10 19H3"}],["circle",{cx:"17",cy:"15",r:"3"}],["path",{d:"m21 19-1.9-1.9"}]],Sa=[["path",{d:"m16 16-3 3 3 3"}],["path",{d:"M3 12h14.5a1 1 0 0 1 0 7H13"}],["path",{d:"M3 19h6"}],["path",{d:"M3 5h18"}]],pA=[["path",{d:"M2 10s3-3 3-8"}],["path",{d:"M22 10s-3-3-3-8"}],["path",{d:"M10 2c0 4.4-3.6 8-8 8"}],["path",{d:"M14 2c0 4.4 3.6 8 8 8"}],["path",{d:"M2 10s2 2 2 5"}],["path",{d:"M22 10s-2 2-2 5"}],["path",{d:"M8 15h8"}],["path",{d:"M2 22v-1a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1"}],["path",{d:"M14 22v-1a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1"}]],lA=[["path",{d:"m10 20-1.25-2.5L6 18"}],["path",{d:"M10 4 8.75 6.5 6 6"}],["path",{d:"M10.585 15H10"}],["path",{d:"M2 12h6.5L10 9"}],["path",{d:"M20 14.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0z"}],["path",{d:"m4 10 1.5 2L4 14"}],["path",{d:"m7 21 3-6-1.5-3"}],["path",{d:"m7 3 3 6h2"}]],iA=[["path",{d:"M12 2v2"}],["path",{d:"M12 8a4 4 0 0 0-1.645 7.647"}],["path",{d:"M2 12h2"}],["path",{d:"M20 14.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0z"}],["path",{d:"m4.93 4.93 1.41 1.41"}],["path",{d:"m6.34 17.66-1.41 1.41"}]],nA=[["path",{d:"M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"}]],eA=[["path",{d:"M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"}],["path",{d:"M17 14V2"}]],rA=[["path",{d:"M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"}],["path",{d:"M7 10v12"}]],oA=[["path",{d:"M12 2v20"}],["path",{d:"m21 16-5 5"}],["path",{d:"m21 21-5-5"}],["path",{d:"M22 12H2"}],["path",{d:"M8 3 3 8"}],["path",{d:"M8 8 3 3"}],["circle",{cx:"18.5",cy:"5.5",r:"2.5"}],["circle",{cx:"5.5",cy:"18.5",r:"2.5"}]],vA=[["path",{d:"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"}],["path",{d:"m9 12 2 2 4-4"}]],$A=[["path",{d:"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"}],["path",{d:"M9 12h6"}]],mA=[["path",{d:"M2 9a3 3 0 1 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 1 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"}],["path",{d:"M9 9h.01"}],["path",{d:"m15 9-6 6"}],["path",{d:"M15 15h.01"}]],yA=[["path",{d:"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"}],["path",{d:"M9 12h6"}],["path",{d:"M12 9v6"}]],sA=[["path",{d:"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"}],["path",{d:"m9.5 14.5 5-5"}]],CA=[["path",{d:"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"}],["path",{d:"m9.5 14.5 5-5"}],["path",{d:"m9.5 9.5 5 5"}]],uA=[["path",{d:"M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"}],["path",{d:"M13 5v2"}],["path",{d:"M13 17v2"}],["path",{d:"M13 11v2"}]],gA=[["path",{d:"M10.5 17h1.227a2 2 0 0 0 1.345-.52L18 12"}],["path",{d:"m12 13.5 3.794.506"}],["path",{d:"m3.173 8.18 11-5a2 2 0 0 1 2.647.993L18.56 8"}],["path",{d:"M6 10V8"}],["path",{d:"M6 14v1"}],["path",{d:"M6 19v2"}],["rect",{x:"2",y:"8",width:"20",height:"13",rx:"2"}]],AA=[["path",{d:"M4 12h.01"}],["path",{d:"M4 16h.01"}],["path",{d:"M4 20h.01"}],["path",{d:"M4 4h.01"}],["path",{d:"M4 8h.01"}],["path",{d:"M9.414 13.414a2 2 0 0 0 1.414.586H19a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1h-8.172a2 2 0 0 0-1.414.586L8 12z"}],["path",{d:"M9.414 21.414a2 2 0 0 0 1.414.586H19a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1h-8.172a2 2 0 0 0-1.414.586L8 20z"}],["path",{d:"M9.414 5.414A2 2 0 0 0 10.828 6H19a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-8.172a2 2 0 0 0-1.414.586L8 4z"}]],HA=[["path",{d:"m3.173 8.18 11-5a2 2 0 0 1 2.647.993L18.56 8"}],["path",{d:"M6 10V8"}],["path",{d:"M6 14v1"}],["path",{d:"M6 19v2"}],["rect",{x:"2",y:"8",width:"20",height:"13",rx:"2"}]],VA=[["path",{d:"M10 2h4"}],["path",{d:"M4.6 11a8 8 0 0 0 1.7 8.7 8 8 0 0 0 8.7 1.7"}],["path",{d:"M7.4 7.4a8 8 0 0 1 10.3 1 8 8 0 0 1 .9 10.2"}],["path",{d:"m2 2 20 20"}],["path",{d:"M12 12v-2"}]],SA=[["path",{d:"M10 2h4"}],["path",{d:"M12 14v-4"}],["path",{d:"M4 13a8 8 0 0 1 8-7 8 8 0 1 1-5.3 14L4 17.6"}],["path",{d:"M9 17H4v5"}]],wA=[["line",{x1:"10",x2:"14",y1:"2",y2:"2"}],["line",{x1:"12",x2:"15",y1:"14",y2:"11"}],["circle",{cx:"12",cy:"14",r:"8"}]],LA=[["circle",{cx:"9",cy:"12",r:"3"}],["rect",{width:"20",height:"14",x:"2",y:"5",rx:"7"}]],fA=[["circle",{cx:"15",cy:"12",r:"3"}],["rect",{width:"20",height:"14",x:"2",y:"5",rx:"7"}]],kA=[["path",{d:"M7 12h13a1 1 0 0 1 1 1 5 5 0 0 1-5 5h-.598a.5.5 0 0 0-.424.765l1.544 2.47a.5.5 0 0 1-.424.765H5.402a.5.5 0 0 1-.424-.765L7 18"}],["path",{d:"M8 18a5 5 0 0 1-5-5V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8"}]],PA=[["path",{d:"M11 10h2"}],["path",{d:"M19 21V10a7 7 0 00-14 0v11"}],["path",{d:"M2 21h20"}],["path",{d:"M9 14h6"}]],BA=[["path",{d:"M10 15h4"}],["path",{d:"m14.5 4.058.571-1.429a1 1 0 011.3-.558l5 2a1 1 0 01.549.534.88.88 0 01.01.765l-2.332 5.83"}],["path",{d:"M15.634 11a2 2 0 00-.186-.34l-1.17-1.757 1.31-1.656a2 2 0 00-2.109-3.165l-2.032.57-1.17-1.758a2 2 0 00-3.664 1.028l-.086 2.11-2.032.568a2 2 0 00-.155 3.8l1.617.6"}],["path",{d:"M4 12.006A1 1 0 014.994 11H19a1 1 0 011 1v7a2 2 0 01-2 2H6a2 2 0 01-2-2z"}]],zA=[["path",{d:"M16 12v4"}],["path",{d:"M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2"}],["path",{d:"M17 6a2 2 0 011.414.586l3 3A2 2 0 0122 11v8a2 2 0 01-2 2H4a2 2 0 01-2-2v-8a2 2 0 01.586-1.414l3-3A2 2 0 017 6z"}],["path",{d:"M2 14h20"}],["path",{d:"M8 12v4"}]],DA=[["path",{d:"M10 3H8"}],["path",{d:"M14.586 11.414 22 4"}],["path",{d:"M15 11a8 8 0 01-.429.4"}],["path",{d:"m2 22 7-7c1.857-1.857 3.714-1.99 5.571-3.6l-1.985-1.986A2 2 0 0114 6a2 2 0 012-2 2 2 0 013.262-1.552l2.152 2.138"}],["path",{d:"M20 15v4"}],["path",{d:"M22 17h-4"}],["path",{d:"M4 5v4"}],["path",{d:"M6 7H2"}],["path",{d:"M9 2v2"}]],FA=[["path",{d:"M15 11c-2 2-4 2-6 4l-7 7"}],["path",{d:"m22 4-7.414 7.414-2-2A2 2 0 0114 6c0-.512.196-1.024.586-1.414A2 2 0 0116 4a2 2 0 013.262-1.552l2.152 2.138"}]],RA=[["path",{d:"M21 4H3"}],["path",{d:"M18 8H6"}],["path",{d:"M19 12H9"}],["path",{d:"M16 16h-6"}],["path",{d:"M11 20H9"}]],bA=[["ellipse",{cx:"12",cy:"11",rx:"3",ry:"2"}],["ellipse",{cx:"12",cy:"12.5",rx:"10",ry:"8.5"}]],TA=[["path",{d:"M12 20v-6"}],["path",{d:"M19.656 14H22"}],["path",{d:"M2 14h12"}],["path",{d:"m2 2 20 20"}],["path",{d:"M20 20H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"}],["path",{d:"M9.656 4H20a2 2 0 0 1 2 2v10.344"}]],qA=[["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2"}],["path",{d:"M2 14h20"}],["path",{d:"M12 20v-6"}]],UA=[["path",{d:"M18.2 12.27 20 6H4l1.8 6.27a1 1 0 0 0 .95.73h10.5a1 1 0 0 0 .96-.73Z"}],["path",{d:"M8 13v9"}],["path",{d:"M16 22v-9"}],["path",{d:"m9 6 1 7"}],["path",{d:"m15 6-1 7"}],["path",{d:"M12 6V2"}],["path",{d:"M13 2h-2"}]],OA=[["path",{d:"M22 7h-2"}],["path",{d:"M6.5 3h11A2.5 2.5 0 0 1 20 5.5V20a1 1 0 0 1-1 1h-9a1 1 0 0 1-1-1V5.5a1 1 0 0 0-5 0V17a1 1 0 0 0 1 1h4"}],["path",{d:"M9 7H2"}]],GA=[["rect",{width:"18",height:"12",x:"3",y:"8",rx:"1"}],["path",{d:"M10 8V5c0-.6-.4-1-1-1H6a1 1 0 0 0-1 1v3"}],["path",{d:"M19 8V5c0-.6-.4-1-1-1h-3a1 1 0 0 0-1 1v3"}]],ZA=[["path",{d:"m10 11 11 .9a1 1 0 0 1 .8 1.1l-.665 4.158a1 1 0 0 1-.988.842H20"}],["path",{d:"M16 18h-5"}],["path",{d:"M18 5a1 1 0 0 0-1 1v5.573"}],["path",{d:"M3 4h8.129a1 1 0 0 1 .99.863L13 11.246"}],["path",{d:"M4 11V4"}],["path",{d:"M7 15h.01"}],["path",{d:"M8 10.1V4"}],["circle",{cx:"18",cy:"18",r:"2"}],["circle",{cx:"7",cy:"15",r:"5"}]],EA=[["path",{d:"M16.05 10.966a5 2.5 0 0 1-8.1 0"}],["path",{d:"m16.923 14.049 4.48 2.04a1 1 0 0 1 .001 1.831l-8.574 3.9a2 2 0 0 1-1.66 0l-8.574-3.91a1 1 0 0 1 0-1.83l4.484-2.04"}],["path",{d:"M16.949 14.14a5 2.5 0 1 1-9.9 0L10.063 3.5a2 2 0 0 1 3.874 0z"}],["path",{d:"M9.194 6.57a5 2.5 0 0 0 5.61 0"}]],WA=[["path",{d:"M10 11.341V10"}],["path",{d:"M14 13v-3"}],["path",{d:"M18 17V8a2 2 0 00-2-2H4a2 2 0 00-2 2v7a2 2 0 002 2h2"}],["path",{d:"M22 15v1a1 1 0 01-1 1H10"}],["path",{d:"M6 11.341V10"}],["circle",{cx:"8",cy:"17",r:"2"}]],IA=[["path",{d:"M2 22V12a10 10 0 1 1 20 0v10"}],["path",{d:"M15 6.8v1.4a3 2.8 0 1 1-6 0V6.8"}],["path",{d:"M10 15h.01"}],["path",{d:"M14 15h.01"}],["path",{d:"M10 19a4 4 0 0 1-4-4v-3a6 6 0 1 1 12 0v3a4 4 0 0 1-4 4Z"}],["path",{d:"m9 19-2 3"}],["path",{d:"m15 19 2 3"}]],XA=[["path",{d:"M8 3.1V7a4 4 0 0 0 8 0V3.1"}],["path",{d:"m9 15-1-1"}],["path",{d:"m15 15 1-1"}],["path",{d:"M9 19c-2.8 0-5-2.2-5-5v-4a8 8 0 0 1 16 0v4c0 2.8-2.2 5-5 5Z"}],["path",{d:"m8 19-2 3"}],["path",{d:"m16 19 2 3"}]],NA=[["path",{d:"M2 17 17 2"}],["path",{d:"m2 14 8 8"}],["path",{d:"m5 11 8 8"}],["path",{d:"m8 8 8 8"}],["path",{d:"m11 5 8 8"}],["path",{d:"m14 2 8 8"}],["path",{d:"M7 22 22 7"}]],KA=[["path",{d:"M12 16v6"}],["path",{d:"M14 20h-4"}],["path",{d:"M18 2h4v4"}],["path",{d:"m2 2 7.17 7.17"}],["path",{d:"M2 5.355V2h3.357"}],["path",{d:"m22 2-7.17 7.17"}],["path",{d:"M8 5 5 8"}],["circle",{cx:"12",cy:"12",r:"4"}]],wa=[["rect",{width:"16",height:"16",x:"4",y:"3",rx:"2"}],["path",{d:"M4 11h16"}],["path",{d:"M12 3v8"}],["path",{d:"m8 19-2 3"}],["path",{d:"m18 22-2-3"}],["path",{d:"M8 15h.01"}],["path",{d:"M16 15h.01"}]],JA=[["path",{d:"M10 11v6"}],["path",{d:"M14 17v-3"}],["path",{d:"M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-1.576.768"}],["path",{d:"M19 6v7.344"}],["path",{d:"m2 2 20 20"}],["path",{d:"M21 6h-9.344"}],["path",{d:"M3 6h3"}],["path",{d:"M5 6v14a2 2 0 002 2h10a2 2 0 002-2v-1"}]],La=[["path",{d:"M10 11v6"}],["path",{d:"M14 11v6"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"}],["path",{d:"M3 6h18"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"}]],QA=[["path",{d:"M8 19a4 4 0 0 1-2.24-7.32A3.5 3.5 0 0 1 9 6.03V6a3 3 0 1 1 6 0v.04a3.5 3.5 0 0 1 3.24 5.65A4 4 0 0 1 16 19Z"}],["path",{d:"M12 19v3"}]],fa=[["path",{d:"M13 8c0-2.76-2.46-5-5.5-5S2 5.24 2 8h2l1-1 1 1h4"}],["path",{d:"M13 7.14A5.82 5.82 0 0 1 16.5 6c3.04 0 5.5 2.24 5.5 5h-3l-1-1-1 1h-3"}],["path",{d:"M5.89 9.71c-2.15 2.15-2.3 5.47-.35 7.43l4.24-4.25.7-.7.71-.71 2.12-2.12c-1.95-1.96-5.27-1.8-7.42.35"}],["path",{d:"M11 15.5c.5 2.5-.17 4.5-1 6.5h4c2-5.5-.5-12-1-14"}]],jA=[["path",{d:"m17 14 3 3.3a1 1 0 0 1-.7 1.7H4.7a1 1 0 0 1-.7-1.7L7 14h-.3a1 1 0 0 1-.7-1.7L9 9h-.2A1 1 0 0 1 8 7.3L12 3l4 4.3a1 1 0 0 1-.8 1.7H15l3 3.3a1 1 0 0 1-.7 1.7H17Z"}],["path",{d:"M12 22v-3"}]],YA=[["path",{d:"M10 10v.2A3 3 0 0 1 8.9 16H5a3 3 0 0 1-1-5.8V10a3 3 0 0 1 6 0Z"}],["path",{d:"M7 16v6"}],["path",{d:"M13 19v3"}],["path",{d:"M12 19h8.3a1 1 0 0 0 .7-1.7L18 14h.3a1 1 0 0 0 .7-1.7L16 9h.2a1 1 0 0 0 .8-1.7L13 3l-1.4 1.5"}]],_A=[["path",{d:"M16 17h6v-6"}],["path",{d:"m22 17-8.5-8.5-5 5L2 7"}]],xA=[["path",{d:"M14.828 14.828 21 21"}],["path",{d:"M21 16v5h-5"}],["path",{d:"m21 3-9 9-4-4-6 6"}],["path",{d:"M21 8V3h-5"}]],aH=[["path",{d:"M16 7h6v6"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17"}]],ka=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"}],["path",{d:"M12 9v4"}],["path",{d:"M12 17h.01"}]],hH=[["path",{d:"M10.17 4.193a2 2 0 0 1 3.666.013"}],["path",{d:"M14 21h2"}],["path",{d:"m15.874 7.743 1 1.732"}],["path",{d:"m18.849 12.952 1 1.732"}],["path",{d:"M21.824 18.18a2 2 0 0 1-1.835 2.824"}],["path",{d:"M4.024 21a2 2 0 0 1-1.839-2.839"}],["path",{d:"m5.136 12.952-1 1.732"}],["path",{d:"M8 21h2"}],["path",{d:"m8.102 7.743-1 1.732"}]],tH=[["path",{d:"M22 18a2 2 0 0 1-2 2H3c-1.1 0-1.3-.6-.4-1.3L20.4 4.3c.9-.7 1.6-.4 1.6.7Z"}]],dH=[["path",{d:"M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"}]],Pa=[["path",{d:"M10 12H8"}],["path",{d:"M16 12h-2"}],["path",{d:"M22 12h-2"}],["path",{d:"M4 12H2"}],["path",{d:"M7.298 20.288A1 1 0 008 22h8a1 1 0 00.703-1.712l-3.991-3.99a1 1 0 00-1.424-.001z"}],["path",{d:"M7.298 3.712A1 1 0 018 2h8a1 1 0 01.703 1.712l-3.991 3.99a1 1 0 01-1.424.001z"}]],Ba=[["path",{d:"M12 14v2"}],["path",{d:"M12 20v2"}],["path",{d:"M12 2v2"}],["path",{d:"M12 8v2"}],["path",{d:"M20.288 16.703A1 1 0 0022 16V8a1 1 0 00-1.712-.703l-3.99 3.991a1 1 0 00-.001 1.424z"}],["path",{d:"M3.712 16.703A1 1 0 012 16V8a1 1 0 011.712-.703l3.99 3.991a1 1 0 01.001 1.424z"}]],cH=[["path",{d:"M10 14.66V17a1 1 0 0 1-1 1 2 2 0 0 0-2 2v2"}],["path",{d:"M14 14.66V17a1 1 0 0 0 1 1 2 2 0 0 1 2 2v2"}],["path",{d:"M17.916 10H19.5A2.5 2.5 0 0 0 22 7.5V5a1 1 0 0 0-1-1h-3"}],["path",{d:"M4 22h16"}],["path",{d:"M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z"}],["path",{d:"M6.084 10H4.5A2.5 2.5 0 0 1 2 7.5V5a1 1 0 0 1 1-1h3"}]],MH=[["path",{d:"M14 19V7a2 2 0 0 0-2-2H9"}],["path",{d:"M15 19H9"}],["path",{d:"M19 19h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.62L18.3 9.38a1 1 0 0 0-.78-.38H14"}],["path",{d:"M2 13v5a1 1 0 0 0 1 1h2"}],["path",{d:"M4 3 2.15 5.15a.495.495 0 0 0 .35.86h2.15a.47.47 0 0 1 .35.86L3 9.02"}],["circle",{cx:"17",cy:"19",r:"2"}],["circle",{cx:"7",cy:"19",r:"2"}]],pH=[["path",{d:"M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"}],["path",{d:"M15 18H9"}],["path",{d:"M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"}],["circle",{cx:"17",cy:"18",r:"2"}],["circle",{cx:"7",cy:"18",r:"2"}]],lH=[["path",{d:"M15 18v3a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1v-3"}],["path",{d:"M17 2a2 2 0 0 1 1.6 3.2A8 8 0 0 0 17 10v6a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2v-6a8 8 0 0 0-1.6-4.8A2 2 0 0 1 7 2z"}],["path",{d:"M7 10a6.47 6.47 0 0 1 5 0 6.47 6.47 0 0 0 5 0"}]],iH=[["path",{d:"M15 4 5 9"}],["path",{d:"m15 8.5-10 5"}],["path",{d:"M18 12a9 9 0 0 1-9 9V3"}]],nH=[["path",{d:"M10 12.01h.01"}],["path",{d:"M18 8v4a8 8 0 0 1-1.07 4"}],["circle",{cx:"10",cy:"12",r:"4"}],["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2"}]],eH=[["path",{d:"m12 10 2 4v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-3a8 8 0 1 0-16 0v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-3l2-4h4Z"}],["path",{d:"M4.82 7.9 8 10"}],["path",{d:"M15.18 7.9 12 10"}],["path",{d:"M16.93 10H20a2 2 0 0 1 0 4H2"}]],rH=[["path",{d:"M15.033 9.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56V7.648a.645.645 0 0 1 .967-.56z"}],["path",{d:"M7 21h10"}],["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2"}]],za=[["path",{d:"M7 21h10"}],["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2"}]],oH=[["path",{d:"m17 2-5 5-5-5"}],["rect",{width:"20",height:"15",x:"2",y:"7",rx:"2"}]],vH=[["path",{d:"M14 16.5a.5.5 0 0 0 .5.5h.5a2 2 0 0 1 0 4H9a2 2 0 0 1 0-4h.5a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5V8a2 2 0 0 1-4 0V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v3a2 2 0 0 1-4 0v-.5a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5Z"}]],$H=[["path",{d:"M12 13v7a2 2 0 0 0 4 0"}],["path",{d:"M12 2v2"}],["path",{d:"M18.656 13h2.336a1 1 0 0 0 .97-1.274 10.284 10.284 0 0 0-12.07-7.51"}],["path",{d:"m2 2 20 20"}],["path",{d:"M5.961 5.957a10.28 10.28 0 0 0-3.922 5.769A1 1 0 0 0 3 13h10"}]],mH=[["path",{d:"M12 13v7a2 2 0 0 0 4 0"}],["path",{d:"M12 2v2"}],["path",{d:"M20.992 13a1 1 0 0 0 .97-1.274 10.284 10.284 0 0 0-19.923 0A1 1 0 0 0 3 13z"}]],yH=[["path",{d:"M6 4v6a6 6 0 0 0 12 0V4"}],["line",{x1:"4",x2:"20",y1:"20",y2:"20"}]],sH=[["path",{d:"M12 4v16"}],["path",{d:"M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2"}],["path",{d:"M9 20h6"}]],CH=[["path",{d:"M9 14 4 9l5-5"}],["path",{d:"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"}]],uH=[["path",{d:"M21 17a9 9 0 0 0-15-6.7L3 13"}],["path",{d:"M3 7v6h6"}],["circle",{cx:"12",cy:"17",r:"1"}]],gH=[["path",{d:"M3 7v6h6"}],["path",{d:"M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"}]],AH=[["path",{d:"M16 12h6"}],["path",{d:"M8 12H2"}],["path",{d:"M12 2v2"}],["path",{d:"M12 8v2"}],["path",{d:"M12 14v2"}],["path",{d:"M12 20v2"}],["path",{d:"m19 15 3-3-3-3"}],["path",{d:"m5 9-3 3 3 3"}]],HH=[["path",{d:"M12 22v-6"}],["path",{d:"M12 8V2"}],["path",{d:"M4 12H2"}],["path",{d:"M10 12H8"}],["path",{d:"M16 12h-2"}],["path",{d:"M22 12h-2"}],["path",{d:"m15 19-3 3-3-3"}],["path",{d:"m15 5-3-3-3 3"}]],VH=[["rect",{x:"11",y:"14",width:"10",height:"7",rx:"2"}],["rect",{x:"3",y:"3",width:"10",height:"7",rx:"2"}]],Da=[["path",{d:"M14 21v-3a2 2 0 0 0-4 0v3"}],["path",{d:"M18 12h.01"}],["path",{d:"M18 16h.01"}],["path",{d:"M22 7a1 1 0 0 0-1-1h-2a2 2 0 0 1-1.143-.359L13.143 2.36a2 2 0 0 0-2.286-.001L6.143 5.64A2 2 0 0 1 5 6H3a1 1 0 0 0-1 1v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2z"}],["path",{d:"M6 12h.01"}],["path",{d:"M6 16h.01"}],["circle",{cx:"12",cy:"10",r:"2"}]],SH=[["path",{d:"M15 7h2a5 5 0 0 1 0 10h-2m-6 0H7A5 5 0 0 1 7 7h2"}]],wH=[["path",{d:"m18.84 12.25 1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.006 5.006 0 0 0-6.95 0l-1.72 1.71"}],["path",{d:"m5.17 11.75-1.71 1.71a5.004 5.004 0 0 0 .12 7.07 5.006 5.006 0 0 0 6.95 0l1.71-1.71"}],["line",{x1:"8",x2:"8",y1:"2",y2:"5"}],["line",{x1:"2",x2:"5",y1:"8",y2:"8"}],["line",{x1:"16",x2:"16",y1:"19",y2:"22"}],["line",{x1:"19",x2:"22",y1:"16",y2:"16"}]],LH=[["path",{d:"m19 5 3-3"}],["path",{d:"m2 22 3-3"}],["path",{d:"M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z"}],["path",{d:"M7.5 13.5 10 11"}],["path",{d:"M10.5 16.5 13 14"}],["path",{d:"m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z"}]],fH=[["path",{d:"M12 3v12"}],["path",{d:"m17 8-5-5-5 5"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}]],kH=[["path",{d:"M6 12h12"}],["rect",{x:"2",y:"8",width:"20",height:"8",rx:"4"}]],PH=[["path",{d:"m16 11 2 2 4-4"}],["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"}],["circle",{cx:"9",cy:"7",r:"4"}]],BH=[["path",{d:"M10 15H6a4 4 0 0 0-4 4v2"}],["path",{d:"m14.305 16.53.923-.382"}],["path",{d:"m15.228 13.852-.923-.383"}],["path",{d:"m16.852 12.228-.383-.923"}],["path",{d:"m16.852 17.772-.383.924"}],["path",{d:"m19.148 12.228.383-.923"}],["path",{d:"m19.53 18.696-.382-.924"}],["path",{d:"m20.772 13.852.924-.383"}],["path",{d:"m20.772 16.148.924.383"}],["circle",{cx:"18",cy:"15",r:"3"}],["circle",{cx:"9",cy:"7",r:"4"}]],zH=[["circle",{cx:"10",cy:"7",r:"1"}],["circle",{cx:"4",cy:"20",r:"1"}],["path",{d:"M4.7 19.3 19 5"}],["path",{d:"m21 3-3 1 2 2Z"}],["path",{d:"M9.26 7.68 5 12l2 5"}],["path",{d:"m10 14 5 2 3.5-3.5"}],["path",{d:"m18 12 1-1 1 1-1 1Z"}]],DH=[["path",{d:"M17 21v-1a2 2 0 00-2-2H9a2 2 0 00-2 2v1"}],["path",{d:"M19 10h1a2 2 0 012 2v1"}],["path",{d:"M5 10H4a2 2 0 00-2 2v1"}],["circle",{cx:"12",cy:"11",r:"3"}],["circle",{cx:"18",cy:"4",r:"2"}],["circle",{cx:"6",cy:"4",r:"2"}]],FH=[["path",{d:"M20 11v6"}],["path",{d:"M20 13h2"}],["path",{d:"M3 21v-2a4 4 0 0 1 4-4h6a4 4 0 0 1 2.072.578"}],["circle",{cx:"10",cy:"7",r:"4"}],["circle",{cx:"20",cy:"19",r:"2"}]],RH=[["path",{d:"M19 16v-2a2 2 0 0 0-4 0v2"}],["path",{d:"M9.5 15H7a4 4 0 0 0-4 4v2"}],["circle",{cx:"10",cy:"7",r:"4"}],["rect",{x:"13",y:"16",width:"8",height:"5",rx:".899"}]],bH=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"}],["circle",{cx:"9",cy:"7",r:"4"}],["line",{x1:"22",x2:"16",y1:"11",y2:"11"}]],TH=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"}],["circle",{cx:"9",cy:"7",r:"4"}],["line",{x1:"19",x2:"19",y1:"8",y2:"14"}],["line",{x1:"22",x2:"16",y1:"11",y2:"11"}]],qH=[["path",{d:"M11.5 15H7a4 4 0 0 0-4 4v2"}],["path",{d:"M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"}],["circle",{cx:"10",cy:"7",r:"4"}]],UH=[["path",{d:"m19 16-3 3"}],["path",{d:"M2 21a8 8 0 0 1 12.664-6.5"}],["path",{d:"M22 19h-6l3 3"}],["circle",{cx:"10",cy:"8",r:"5"}]],Fa=[["path",{d:"M2 21a8 8 0 0 1 13.292-6"}],["circle",{cx:"10",cy:"8",r:"5"}],["path",{d:"m16 19 2 2 4-4"}]],Ra=[["path",{d:"m14.305 19.53.923-.382"}],["path",{d:"m15.228 16.852-.923-.383"}],["path",{d:"m16.852 15.228-.383-.923"}],["path",{d:"m16.852 20.772-.383.924"}],["path",{d:"m19.148 15.228.383-.923"}],["path",{d:"m19.53 21.696-.382-.924"}],["path",{d:"M2 21a8 8 0 0 1 10.434-7.62"}],["path",{d:"m20.772 16.852.924-.383"}],["path",{d:"m20.772 19.148.924.383"}],["circle",{cx:"10",cy:"8",r:"5"}],["circle",{cx:"18",cy:"18",r:"3"}]],OH=[["path",{d:"M17 21a5 5 0 00-10 0"}],["path",{d:"M22 10.5a3.5 3.5 0 00-5.507-2.868"}],["path",{d:"M7.507 7.632A3.5 3.5 0 002 10.5"}],["circle",{cx:"12",cy:"13",r:"3"}],["circle",{cx:"18.5",cy:"4.5",r:"2.5"}],["circle",{cx:"5.5",cy:"4.5",r:"2.5"}]],GH=[["path",{d:"M19 11v6"}],["path",{d:"M19 13h2"}],["path",{d:"M2 21a8 8 0 0 1 12.868-6.349"}],["circle",{cx:"10",cy:"8",r:"5"}],["circle",{cx:"19",cy:"19",r:"2"}]],ba=[["path",{d:"M2 21a8 8 0 0 1 13.292-6"}],["circle",{cx:"10",cy:"8",r:"5"}],["path",{d:"M22 19h-6"}]],ZH=[["path",{d:"M2 21a8 8 0 0 1 10.821-7.487"}],["path",{d:"M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"}],["circle",{cx:"10",cy:"8",r:"5"}]],Ta=[["path",{d:"M2 21a8 8 0 0 1 13.292-6"}],["circle",{cx:"10",cy:"8",r:"5"}],["path",{d:"M19 16v6"}],["path",{d:"M22 19h-6"}]],EH=[["circle",{cx:"10",cy:"8",r:"5"}],["path",{d:"M2 21a8 8 0 0 1 10.434-7.62"}],["circle",{cx:"18",cy:"18",r:"3"}],["path",{d:"m22 22-1.9-1.9"}]],WH=[["path",{d:"M16.051 12.616a1 1 0 0 1 1.909.024l.737 1.452a1 1 0 0 0 .737.535l1.634.256a1 1 0 0 1 .588 1.806l-1.172 1.168a1 1 0 0 0-.282.866l.259 1.613a1 1 0 0 1-1.541 1.134l-1.465-.75a1 1 0 0 0-.912 0l-1.465.75a1 1 0 0 1-1.539-1.133l.258-1.613a1 1 0 0 0-.282-.867l-1.156-1.152a1 1 0 0 1 .572-1.822l1.633-.256a1 1 0 0 0 .737-.535z"}],["path",{d:"M2 21a8 8 0 0 1 6.778-7.905"}],["path",{d:"M8.927 12.885a5 5 0 1 1 6.061-4.535"}]],qa=[["path",{d:"m16.5 16.5 5 5"}],["path",{d:"M2 21a8 8 0 0 1 11.531-7.18"}],["path",{d:"m21.5 16.5-5 5"}],["circle",{cx:"10",cy:"8",r:"5"}]],Ua=[["circle",{cx:"12",cy:"8",r:"5"}],["path",{d:"M20 21a8 8 0 0 0-16 0"}]],IH=[["circle",{cx:"10",cy:"7",r:"4"}],["path",{d:"M10.3 15H7a4 4 0 0 0-4 4v2"}],["circle",{cx:"17",cy:"17",r:"3"}],["path",{d:"m21 21-1.9-1.9"}]],XH=[["path",{d:"M10 15H6a4 4 0 0 0-4 4v2"}],["path",{d:"M22 17.5c0 2.499-1.75 3.749-3.83 4.474a.5.5 0 0 1-.335-.005c-2.085-.72-3.835-1.97-3.835-4.47V14a.5.5 0 0 1 .5-.499c1 0 2.25-.6 3.12-1.36a.6.6 0 0 1 .76-.001c.875.765 2.12 1.36 3.12 1.36a.5.5 0 0 1 .5.5z"}],["circle",{cx:"9",cy:"7",r:"4"}]],NH=[["path",{d:"M16.051 12.616a1 1 0 0 1 1.909.024l.737 1.452a1 1 0 0 0 .737.535l1.634.256a1 1 0 0 1 .588 1.806l-1.172 1.168a1 1 0 0 0-.282.866l.259 1.613a1 1 0 0 1-1.541 1.134l-1.465-.75a1 1 0 0 0-.912 0l-1.465.75a1 1 0 0 1-1.539-1.133l.258-1.613a1 1 0 0 0-.282-.866l-1.156-1.153a1 1 0 0 1 .572-1.822l1.633-.256a1 1 0 0 0 .737-.535z"}],["path",{d:"M8 15H7a4 4 0 0 0-4 4v2"}],["circle",{cx:"10",cy:"7",r:"4"}]],KH=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"}],["circle",{cx:"9",cy:"7",r:"4"}],["line",{x1:"17",x2:"22",y1:"8",y2:"13"}],["line",{x1:"22",x2:"17",y1:"8",y2:"13"}]],JH=[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"}],["circle",{cx:"12",cy:"7",r:"4"}]],QH=[["path",{d:"M12.925 3.047a9 9 0 017.616 6.11"}],["path",{d:"M13 22a2 2 0 012-2h4a2 2 0 012 2"}],["path",{d:"M3 12a2 2 0 012-2h4a2 2 0 012 2"}],["path",{d:"M3.889 15.9a9 9 0 005.247 4.632"}],["circle",{cx:"17",cy:"14",r:"2"}],["circle",{cx:"7",cy:"4",r:"2"}]],jH=[["path",{d:"M20.453 8.911a9 9 0 00-6.696-5.738"}],["path",{d:"M21 22a4 4 0 00-8 0"}],["path",{d:"M3 12a4 4 0 017.963-.543"}],["path",{d:"M3.889 15.9a9 9 0 005.247 4.632"}],["circle",{cx:"17",cy:"15",r:"3"}],["circle",{cx:"7",cy:"5",r:"3"}]],Oa=[["path",{d:"M18 21a8 8 0 0 0-16 0"}],["circle",{cx:"10",cy:"8",r:"5"}],["path",{d:"M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3"}]],YH=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87"}],["circle",{cx:"9",cy:"7",r:"4"}]],Ga=[["path",{d:"m16 2-2.3 2.3a3 3 0 0 0 0 4.2l1.8 1.8a3 3 0 0 0 4.2 0L22 8"}],["path",{d:"M15 15 3.3 3.3a4.2 4.2 0 0 0 0 6l7.3 7.3c.7.7 2 .7 2.8 0L15 15Zm0 0 7 7"}],["path",{d:"m2.1 21.8 6.4-6.3"}],["path",{d:"m19 5-7 7"}]],Za=[["path",{d:"M3 2v7c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2V2"}],["path",{d:"M7 2v20"}],["path",{d:"M21 15V2a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3Zm0 0v7"}]],_H=[["path",{d:"M12 2v20"}],["path",{d:"M2 5h20"}],["path",{d:"M3 3v2"}],["path",{d:"M7 3v2"}],["path",{d:"M17 3v2"}],["path",{d:"M21 3v2"}],["path",{d:"m19 5-7 7-7-7"}]],xH=[["path",{d:"M13 6v5a1 1 0 0 0 1 1h6.102a1 1 0 0 1 .712.298l.898.91a1 1 0 0 1 .288.702V17a1 1 0 0 1-1 1h-3"}],["path",{d:"M5 18H3a1 1 0 0 1-1-1V8a2 2 0 0 1 2-2h12c1.1 0 2.1.8 2.4 1.8l1.176 4.2"}],["path",{d:"M9 18h5"}],["circle",{cx:"16",cy:"18",r:"2"}],["circle",{cx:"7",cy:"18",r:"2"}]],aV=[["path",{d:"M8 21s-4-3-4-9 4-9 4-9"}],["path",{d:"M16 3s4 3 4 9-4 9-4 9"}],["line",{x1:"15",x2:"9",y1:"9",y2:"15"}],["line",{x1:"9",x2:"15",y1:"9",y2:"15"}]],hV=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2"}],["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor"}],["path",{d:"m7.9 7.9 2.7 2.7"}],["circle",{cx:"16.5",cy:"7.5",r:".5",fill:"currentColor"}],["path",{d:"m13.4 10.6 2.7-2.7"}],["circle",{cx:"7.5",cy:"16.5",r:".5",fill:"currentColor"}],["path",{d:"m7.9 16.1 2.7-2.7"}],["circle",{cx:"16.5",cy:"16.5",r:".5",fill:"currentColor"}],["path",{d:"m13.4 13.4 2.7 2.7"}],["circle",{cx:"12",cy:"12",r:"2"}]],tV=[["path",{d:"m12.828 4.813 5.344 2.375"}],["path",{d:"m15.769 18.153 3.461-8.306"}],["path",{d:"m5.687 14.074 7.625 4.852"}],["path",{d:"M9.772 5.579 5.228 11.42"}],["circle",{cx:"11",cy:"4",r:"2"}],["circle",{cx:"15",cy:"20",r:"2"}],["circle",{cx:"20",cy:"8",r:"2"}],["circle",{cx:"4",cy:"13",r:"2"}]],dV=[["path",{d:"M17.055 4.533a24 24 0 00-10.11 0"}],["path",{d:"M19.467 17.055a24 24 0 000-10.11"}],["path",{d:"M4.533 6.945a24 24 0 000 10.11"}],["path",{d:"M6.945 19.467a24 24 0 0010.11 0"}],["circle",{cx:"19",cy:"19",r:"2"}],["circle",{cx:"19",cy:"5",r:"2"}],["circle",{cx:"5",cy:"19",r:"2"}],["circle",{cx:"5",cy:"5",r:"2"}]],cV=[["path",{d:"M16 8q6 0 6-6-6 0-6 6"}],["path",{d:"M17.41 3.59a10 10 0 1 0 3 3"}],["path",{d:"M2 2a26.6 26.6 0 0 1 10 20c.9-6.82 1.5-9.5 4-14"}]],MV=[["path",{d:"M18 11c-1.5 0-2.5.5-3 2"}],["path",{d:"M4 6a2 2 0 0 0-2 2v4a5 5 0 0 0 5 5 8 8 0 0 1 5 2 8 8 0 0 1 5-2 5 5 0 0 0 5-5V8a2 2 0 0 0-2-2h-3a8 8 0 0 0-5 2 8 8 0 0 0-5-2z"}],["path",{d:"M6 11c1.5 0 2.5.5 3 2"}]],pV=[["path",{d:"M10 20h4"}],["path",{d:"M12 16v6"}],["path",{d:"M17 2h4v4"}],["path",{d:"m21 2-5.46 5.46"}],["circle",{cx:"12",cy:"11",r:"5"}]],lV=[["path",{d:"M12 15v7"}],["path",{d:"M9 19h6"}],["circle",{cx:"12",cy:"9",r:"6"}]],iV=[["path",{d:"m2 8 2 2-2 2 2 2-2 2"}],["path",{d:"m22 8-2 2 2 2-2 2 2 2"}],["path",{d:"M8 8v10c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2"}],["path",{d:"M16 10.34V6c0-.55-.45-1-1-1h-4.34"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],nV=[["path",{d:"m2 8 2 2-2 2 2 2-2 2"}],["path",{d:"m22 8-2 2 2 2-2 2 2 2"}],["rect",{width:"8",height:"14",x:"8",y:"5",rx:"1"}]],eV=[["path",{d:"M10.66 6H14a2 2 0 0 1 2 2v2.5l5.248-3.062A.5.5 0 0 1 22 7.87v8.196"}],["path",{d:"M16 16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2"}],["path",{d:"m2 2 20 20"}]],rV=[["path",{d:"m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"}],["rect",{x:"2",y:"6",width:"14",height:"12",rx:"2"}]],oV=[["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2"}],["path",{d:"M2 8h20"}],["circle",{cx:"8",cy:"14",r:"2"}],["path",{d:"M8 12h8"}],["circle",{cx:"16",cy:"14",r:"2"}]],vV=[["path",{d:"M21 17v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2"}],["path",{d:"M21 7V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2"}],["circle",{cx:"12",cy:"12",r:"1"}],["path",{d:"M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0"}]],$V=[["path",{d:"M10.01 10h.01"}],["path",{d:"M12 14.991h.01"}],["path",{d:"M12 22v-3"}],["path",{d:"M12 2v3"}],["path",{d:"M13 22h-2"}],["path",{d:"M13 2h-2"}],["path",{d:"m16.5 19.794-1-1.733"}],["path",{d:"m16.5 4.205-1 1.732"}],["path",{d:"M18.891 13.235a7 7 0 00-8.126-8.126"}],["path",{d:"m19.794 7.5-1.732 1"}],["path",{d:"M2 12h3"}],["path",{d:"M2 13v-2"}],["path",{d:"m2 2 20 20"}],["path",{d:"M22 12h-3"}],["path",{d:"M22 13v-2"}],["path",{d:"m4.206 16.5 1.732-1"}],["path",{d:"m4.206 7.5 1.732 1"}],["path",{d:"M7.05 7.05a7 7 0 009.9 9.9"}],["path",{d:"m7.5 19.794 1-1.733"}],["path",{d:"M9 12h.01"}]],mV=[["path",{d:"M12 14.991h.01"}],["path",{d:"M12 22v-3"}],["path",{d:"M12 2v3"}],["path",{d:"M13 22h-2"}],["path",{d:"M13 2h-2"}],["path",{d:"M13.99 10H14"}],["path",{d:"m16.5 19.794-1-1.733"}],["path",{d:"m16.5 4.205-1 1.732"}],["path",{d:"m19.794 16.5-1.732-1"}],["path",{d:"m19.794 7.5-1.732 1"}],["path",{d:"M2 12h3"}],["path",{d:"M2 13v-2"}],["path",{d:"M22 12h-3"}],["path",{d:"M22 13v-2"}],["path",{d:"m4.206 16.5 1.732-1"}],["path",{d:"m4.206 7.5 1.732 1"}],["path",{d:"m7.5 19.794 1-1.733"}],["path",{d:"m7.5 4.205 1 1.732"}],["path",{d:"M9 12h.01"}],["circle",{cx:"12",cy:"12",r:"7"}]],yV=[["path",{d:"M11 7a16 16 20 0 1 10.98 4.362"}],["path",{d:"M12 12a13 13 0 0 1-8.66 5"}],["path",{d:"M16.83 13.634a16 16 0 0 1-9.267 7.328"}],["path",{d:"M20.66 17A13 13 0 0 0 12 12a13 13 0 0 1 0-10"}],["path",{d:"M8.17 15.366a16 16 0 0 1-1.713-11.69"}],["circle",{cx:"12",cy:"12",r:"10"}]],sV=[["circle",{cx:"6",cy:"12",r:"4"}],["circle",{cx:"18",cy:"12",r:"4"}],["line",{x1:"6",x2:"18",y1:"16",y2:"16"}]],CV=[["path",{d:"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"}],["path",{d:"M16 9a5 5 0 0 1 0 6"}]],uV=[["path",{d:"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"}],["path",{d:"M16 9a5 5 0 0 1 0 6"}],["path",{d:"M19.364 18.364a9 9 0 0 0 0-12.728"}]],gV=[["path",{d:"M16 9a5 5 0 0 1 .95 2.293"}],["path",{d:"M19.364 5.636a9 9 0 0 1 1.889 9.96"}],["path",{d:"m2 2 20 20"}],["path",{d:"m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11"}],["path",{d:"M9.828 4.172A.686.686 0 0 1 11 4.657v.686"}]],AV=[["path",{d:"M11 4.702a.7.7 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.7.7 0 0 0 11 19.298z"}],["path",{d:"m16.5 14.5 5-5"}],["path",{d:"m16.5 9.5 5 5"}]],HV=[["path",{d:"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"}]],VV=[["path",{d:"m9 12 2 2 4-4"}],["path",{d:"M5 7c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v12H5V7Z"}],["path",{d:"M22 19H2"}]],SV=[["path",{d:"M3 11h3.75a2 2 0 0 1 1.6.8l.45.6a4 4 0 0 0 6.4 0l.45-.6a2 2 0 0 1 1.6-.8H21"}],["path",{d:"M3 7h18"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}]],Ea=[["path",{d:"M17 14h.01"}],["path",{d:"M7 7h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14"}]],wV=[["path",{d:"M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1"}],["path",{d:"M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4"}]],LV=[["path",{d:"M12 17v4"}],["path",{d:"M8 21h8"}],["path",{d:"m9 17 6.1-6.1a2 2 0 0 1 2.81.01L22 15"}],["circle",{cx:"8",cy:"9",r:"2"}],["rect",{x:"2",y:"3",width:"20",height:"14",rx:"2"}]],Wa=[["path",{d:"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72"}],["path",{d:"m14 7 3 3"}],["path",{d:"M5 6v4"}],["path",{d:"M19 14v4"}],["path",{d:"M10 2v2"}],["path",{d:"M7 8H3"}],["path",{d:"M21 16h-4"}],["path",{d:"M11 3H9"}]],fV=[["path",{d:"M15 4V2"}],["path",{d:"M15 16v-2"}],["path",{d:"M8 9h2"}],["path",{d:"M20 9h2"}],["path",{d:"M17.8 11.8 19 13"}],["path",{d:"M15 9h.01"}],["path",{d:"M17.8 6.2 19 5"}],["path",{d:"m3 21 9-9"}],["path",{d:"M12.2 6.2 11 5"}]],kV=[["path",{d:"M18 21V10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v11"}],["path",{d:"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 1.132-1.803l7.95-3.974a2 2 0 0 1 1.837 0l7.948 3.974A2 2 0 0 1 22 8z"}],["path",{d:"M6 13h12"}],["path",{d:"M6 17h12"}]],PV=[["path",{d:"M3 6h3"}],["path",{d:"M17 6h.01"}],["rect",{width:"18",height:"20",x:"3",y:"2",rx:"2"}],["circle",{cx:"12",cy:"13",r:"5"}],["path",{d:"M12 18a2.5 2.5 0 0 0 0-5 2.5 2.5 0 0 1 0-5"}]],BV=[["path",{d:"M12 10L12 2"}],["path",{d:"M16 6L12 10L8 6"}],["path",{d:"M2 15C2.6 15.5 3.2 16 4.5 16C7 16 7 14 9.5 14C12.1 14 11.9 16 14.5 16C17 16 17 14 19.5 14C20.8 14 21.4 14.5 22 15"}],["path",{d:"M2 21C2.6 21.5 3.2 22 4.5 22C7 22 7 20 9.5 20C12.1 20 11.9 22 14.5 22C17 22 17 20 19.5 20C20.8 20 21.4 20.5 22 21"}]],zV=[["path",{d:"M12 10v2.2l1.6 1"}],["path",{d:"m16.13 7.66-.81-4.05a2 2 0 0 0-2-1.61h-2.68a2 2 0 0 0-2 1.61l-.78 4.05"}],["path",{d:"m7.88 16.36.8 4a2 2 0 0 0 2 1.61h2.72a2 2 0 0 0 2-1.61l.81-4.05"}],["circle",{cx:"12",cy:"12",r:"6"}]],DV=[["path",{d:"M12 2v8"}],["path",{d:"M2 15c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"}],["path",{d:"M2 21c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"}],["path",{d:"m8 6 4-4 4 4"}]],Ia=[["path",{d:"M2 12q2.5 2 5 0t5 0 5 0 5 0"}],["path",{d:"M2 19q2.5 2 5 0t5 0 5 0 5 0"}],["path",{d:"M2 5q2.5 2 5 0t5 0 5 0 5 0"}]],FV=[["path",{d:"M19 5a2 2 0 0 0-2 2v11"}],["path",{d:"M2 18c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"}],["path",{d:"M7 13h10"}],["path",{d:"M7 9h10"}],["path",{d:"M9 5a2 2 0 0 0-2 2v11"}]],RV=[["path",{d:"M12 2q2 2.5 0 5t0 5 0 5 0 5"}],["path",{d:"M19 2q2 2.5 0 5t0 5 0 5 0 5"}],["path",{d:"M5 2q2 2.5 0 5t0 5 0 5 0 5"}]],bV=[["path",{d:"m10.586 5.414-5.172 5.172"}],["path",{d:"m18.586 13.414-5.172 5.172"}],["path",{d:"M6 12h12"}],["circle",{cx:"12",cy:"20",r:"2"}],["circle",{cx:"12",cy:"4",r:"2"}],["circle",{cx:"20",cy:"12",r:"2"}],["circle",{cx:"4",cy:"12",r:"2"}]],TV=[["path",{d:"M12 22v-4"}],["path",{d:"M12.754 7.096a3 3 0 0 1 2.15 2.15"}],["path",{d:"M12.863 12.873a3 3 0 0 1-3.736-3.735"}],["path",{d:"M16.566 16.57A8 8 0 0 1 5.43 5.433"}],["path",{d:"m2 2 20 20"}],["path",{d:"M7 22h10"}],["path",{d:"M8.478 2.817a8 8 0 0 1 10.705 10.705"}]],qV=[["circle",{cx:"12",cy:"10",r:"8"}],["circle",{cx:"12",cy:"10",r:"3"}],["path",{d:"M7 22h10"}],["path",{d:"M12 22v-4"}]],UV=[["path",{d:"M17 17h-5c-1.09-.02-1.94.92-2.5 1.9A3 3 0 1 1 2.57 15"}],["path",{d:"M9 3.4a4 4 0 0 1 6.52.66"}],["path",{d:"m6 17 3.1-5.8a2.5 2.5 0 0 0 .057-2.05"}],["path",{d:"M20.3 20.3a4 4 0 0 1-2.3.7"}],["path",{d:"M18.6 13a4 4 0 0 1 3.357 3.414"}],["path",{d:"m12 6 .6 1"}],["path",{d:"m2 2 20 20"}]],OV=[["path",{d:"M6.5 8a2 2 0 0 0-1.906 1.46L2.1 18.5A2 2 0 0 0 4 21h16a2 2 0 0 0 1.925-2.54L19.4 9.5A2 2 0 0 0 17.48 8z"}],["path",{d:"M7.999 15a2.5 2.5 0 0 1 4 0 2.5 2.5 0 0 0 4 0"}],["circle",{cx:"12",cy:"5",r:"3"}]],GV=[["circle",{cx:"12",cy:"5",r:"3"}],["path",{d:"M6.5 8a2 2 0 0 0-1.905 1.46L2.1 18.5A2 2 0 0 0 4 21h16a2 2 0 0 0 1.925-2.54L19.4 9.5A2 2 0 0 0 17.48 8Z"}]],ZV=[["path",{d:"m2 22 10-10"}],["path",{d:"m16 8-1.17 1.17"}],["path",{d:"M3.47 12.53 5 11l1.53 1.53a3.5 3.5 0 0 1 0 4.94L5 19l-1.53-1.53a3.5 3.5 0 0 1 0-4.94Z"}],["path",{d:"m8 8-.53.53a3.5 3.5 0 0 0 0 4.94L9 15l1.53-1.53c.55-.55.88-1.25.98-1.97"}],["path",{d:"M10.91 5.26c.15-.26.34-.51.56-.73L13 3l1.53 1.53a3.5 3.5 0 0 1 .28 4.62"}],["path",{d:"M20 2h2v2a4 4 0 0 1-4 4h-2V6a4 4 0 0 1 4-4Z"}],["path",{d:"M11.47 17.47 13 19l-1.53 1.53a3.5 3.5 0 0 1-4.94 0L5 19l1.53-1.53a3.5 3.5 0 0 1 4.94 0Z"}],["path",{d:"m16 16-.53.53a3.5 3.5 0 0 1-4.94 0L9 15l1.53-1.53a3.49 3.49 0 0 1 1.97-.98"}],["path",{d:"M18.74 13.09c.26-.15.51-.34.73-.56L21 11l-1.53-1.53a3.5 3.5 0 0 0-4.62-.28"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],EV=[["path",{d:"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2"}],["path",{d:"m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06"}],["path",{d:"m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8"}]],WV=[["path",{d:"M2 22 16 8"}],["path",{d:"M3.47 12.53 5 11l1.53 1.53a3.5 3.5 0 0 1 0 4.94L5 19l-1.53-1.53a3.5 3.5 0 0 1 0-4.94Z"}],["path",{d:"M7.47 8.53 9 7l1.53 1.53a3.5 3.5 0 0 1 0 4.94L9 15l-1.53-1.53a3.5 3.5 0 0 1 0-4.94Z"}],["path",{d:"M11.47 4.53 13 3l1.53 1.53a3.5 3.5 0 0 1 0 4.94L13 11l-1.53-1.53a3.5 3.5 0 0 1 0-4.94Z"}],["path",{d:"M20 2h2v2a4 4 0 0 1-4 4h-2V6a4 4 0 0 1 4-4Z"}],["path",{d:"M11.47 17.47 13 19l-1.53 1.53a3.5 3.5 0 0 1-4.94 0L5 19l1.53-1.53a3.5 3.5 0 0 1 4.94 0Z"}],["path",{d:"M15.47 13.47 17 15l-1.53 1.53a3.5 3.5 0 0 1-4.94 0L9 15l1.53-1.53a3.5 3.5 0 0 1 4.94 0Z"}],["path",{d:"M19.47 9.47 21 11l-1.53 1.53a3.5 3.5 0 0 1-4.94 0L13 11l1.53-1.53a3.5 3.5 0 0 1 4.94 0Z"}]],IV=[["path",{d:"M10 6v4"}],["path",{d:"M21 6a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-5.675A7 7 0 1 1 9 6z"}]],XV=[["circle",{cx:"7",cy:"12",r:"3"}],["path",{d:"M10 9v6"}],["circle",{cx:"17",cy:"12",r:"3"}],["path",{d:"M14 7v8"}],["path",{d:"M22 17v1c0 .5-.5 1-1 1H3c-.5 0-1-.5-1-1v-1"}]],NV=[["path",{d:"m14.305 19.53.923-.382"}],["path",{d:"m15.228 16.852-.923-.383"}],["path",{d:"m16.852 15.228-.383-.923"}],["path",{d:"m16.852 20.772-.383.924"}],["path",{d:"m19.148 15.228.383-.923"}],["path",{d:"m19.53 21.696-.382-.924"}],["path",{d:"M2 8.82a15 15 0 0120 0"}],["path",{d:"m20.772 16.852.924-.383"}],["path",{d:"m20.772 19.148.924.383"}],["path",{d:"M5 12.859a10 10 0 0110.18-2.342"}],["path",{d:"M8.5 16.429a5 5 0 011.998-1.2"}],["circle",{cx:"18",cy:"18",r:"3"}]],KV=[["path",{d:"M12 20h.01"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0"}]],JV=[["path",{d:"M2 8.82a15 15 0 0120 0"}],["path",{d:"M20 16v-2a2 2 0 00-4 0v2"}],["path",{d:"M5 12.859a10 10 0 018.436-2.756"}],["path",{d:"M8.5 16.429a5 5 0 011.794-1.13"}],["rect",{x:"14",y:"16",width:"8",height:"5",rx:"1"}]],QV=[["path",{d:"M12 20h.01"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0"}]],jV=[["path",{d:"M12 20h.01"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0"}],["path",{d:"M5 12.859a10 10 0 0 1 5.17-2.69"}],["path",{d:"M19 12.859a10 10 0 0 0-2.007-1.523"}],["path",{d:"M2 8.82a15 15 0 0 1 4.177-2.643"}],["path",{d:"M22 8.82a15 15 0 0 0-11.288-3.764"}],["path",{d:"m2 2 20 20"}]],YV=[["path",{d:"M2 8.82a15 15 0 0 1 20 0"}],["path",{d:"M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"}],["path",{d:"M5 12.859a10 10 0 0 1 10.5-2.222"}],["path",{d:"M8.5 16.429a5 5 0 0 1 3-1.406"}]],_V=[["path",{d:"M11.965 10.105v4L13.5 12.5a5 5 0 0 1 8 1.5"}],["path",{d:"M11.965 14.105h4"}],["path",{d:"M17.965 18.105h4L20.43 19.71a5 5 0 0 1-8-1.5"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0"}],["path",{d:"M21.965 22.105v-4"}],["path",{d:"M5 12.86a10 10 0 0 1 3-2.032"}],["path",{d:"M8.5 16.429h.01"}]],xV=[["path",{d:"M12 20h.01"}]],aS=[["path",{d:"M12 20h.01"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0"}]],hS=[["path",{d:"M10 2v8"}],["path",{d:"M12.8 21.6A2 2 0 1 0 14 18H2"}],["path",{d:"M17.5 10a2.5 2.5 0 1 1 2 4H2"}],["path",{d:"m6 6 4 -4 4 4"}]],tS=[["path",{d:"M12.8 19.6A2 2 0 1 0 14 16H2"}],["path",{d:"M17.5 8a2.5 2.5 0 1 1 2 4H2"}],["path",{d:"M9.8 4.4A2 2 0 1 1 11 8H2"}]],dS=[["path",{d:"M10 2v8"}],["path",{d:"M12.8 21.6A2 2 0 1 0 14 18H2"}],["path",{d:"M17.5 10a2.5 2.5 0 1 1 2 4H2"}],["path",{d:"m6 6 4 4 4-4"}]],cS=[["path",{d:"M8 22h8"}],["path",{d:"M7 10h3m7 0h-1.343"}],["path",{d:"M12 15v7"}],["path",{d:"M7.307 7.307A12.33 12.33 0 0 0 7 10a5 5 0 0 0 7.391 4.391M8.638 2.981C8.75 2.668 8.872 2.34 9 2h6c1.5 4 2 6 2 8 0 .407-.05.809-.145 1.198"}],["line",{x1:"2",x2:"22",y1:"2",y2:"22"}]],MS=[["path",{d:"M8 22h8"}],["path",{d:"M7 10h10"}],["path",{d:"M12 15v7"}],["path",{d:"M12 15a5 5 0 0 0 5-5c0-2-.5-4-2-8H9c-1.5 4-2 6-2 8a5 5 0 0 0 5 5Z"}]],pS=[["rect",{width:"8",height:"8",x:"3",y:"3",rx:"2"}],["path",{d:"M7 11v4a2 2 0 0 0 2 2h4"}],["rect",{width:"8",height:"8",x:"13",y:"13",rx:"2"}]],lS=[["path",{d:"m19 12-1.5 3"}],["path",{d:"M19.63 18.81 22 20"}],["path",{d:"M6.47 8.23a1.68 1.68 0 0 1 2.44 1.93l-.64 2.08a6.76 6.76 0 0 0 10.16 7.67l.42-.27a1 1 0 1 0-2.73-4.21l-.42.27a1.76 1.76 0 0 1-2.63-1.99l.64-2.08A6.66 6.66 0 0 0 3.94 3.9l-.7.4a1 1 0 1 0 2.55 4.34z"}]],iS=[["path",{d:"M10.747 5.093a6 6 0 0 1 6.841-2.882c.438.12.54.662.219.984L14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-2.882 6.842"}],["path",{d:"m13.5 13.5-7.88 7.88a1 1 0 0 1-2.999-3l7.88-7.88"}],["path",{d:"m2 2 20 20"}]],nS=[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z"}]],eS=[["path",{d:"M18 4H6"}],["path",{d:"M18 8 6 20"}],["path",{d:"m6 8 12 12"}]],rS=[["path",{d:"M18 6 6 18"}],["path",{d:"m6 6 12 12"}]],oS=[["path",{d:"M15.914 4a1.5 1.5 0 00-2.474-1.561l-9 9A1.5 1.5 0 005.5 14h4.002a.5.5 0 01.471.666L8.086 20a1.5 1.5 0 002.475 1.56l9-9A1.5 1.5 0 0018.5 10h-3.997a.5.5 0 01-.472-.667z"}]],vS=[["path",{d:"M10.768 5.111 13.44 2.44a1.5 1.5 0 012.474 1.561l-1.633 4.625"}],["path",{d:"m18.889 13.232.672-.672A1.5 1.5 0 0018.5 10h-2.844"}],["path",{d:"m2 2 20 20"}],["path",{d:"m7.94 7.94-3.5 3.499A1.5 1.5 0 005.5 14h4.002a.5.5 0 01.471.666L8.086 20a1.5 1.5 0 002.475 1.56l5.5-5.5"}]],$S=[["path",{d:"m2 10 2.456-3.684a.7.7 0 0 1 1.106-.013l2.39 3.413a.7.7 0 0 0 1.096-.001l2.402-3.432a.7.7 0 0 1 1.098 0l2.402 3.432a.7.7 0 0 0 1.098 0l2.389-3.413a.7.7 0 0 1 1.106.013L22 10"}],["path",{d:"m2 18.002 2.456-3.684a.7.7 0 0 1 1.106-.013l2.39 3.413a.7.7 0 0 0 1.097 0l2.402-3.432a.7.7 0 0 1 1.098 0l2.402 3.432a.7.7 0 0 0 1.098 0l2.389-3.413a.7.7 0 0 1 1.106.013L22 18.002"}]],mS=[["path",{d:"M12 7.5a4.5 4.5 0 1 1 5 4.5"}],["path",{d:"M7 12a4.5 4.5 0 1 1 5-4.5V21"}]],yS=[["path",{d:"M21 14.5A9 6.5 0 0 1 5.5 19"}],["path",{d:"M3 9.5A9 6.5 0 0 1 18.5 5"}],["circle",{cx:"17.5",cy:"14.5",r:"3.5"}],["circle",{cx:"6.5",cy:"9.5",r:"3.5"}]],sS=[["path",{d:"M11 21a3 3 0 0 0 3-3V6.5a1 1 0 0 0-7 0"}],["path",{d:"M7 19V6a3 3 0 0 0-3-3h0"}],["circle",{cx:"17",cy:"17",r:"3"}]],CS=[["path",{d:"M16 4.525v14.948"}],["path",{d:"M20 3A17 17 0 0 1 4 3"}],["path",{d:"M4 21a17 17 0 0 1 16 0"}],["path",{d:"M8 4.525v14.948"}]],uS=[["path",{d:"M10 16c0-4-3-4.5-3-8a5 5 0 0 1 10 0c0 3.466-3 6.196-3 10a3 3 0 0 0 6 0"}],["circle",{cx:"7",cy:"16",r:"3"}]],gS=[["path",{d:"M3 10A6.06 6.06 0 0 1 12 10 A6.06 6.06 0 0 0 21 10"}],["path",{d:"M6 3v12a6 6 0 0 0 12 0V3"}]],AS=[["path",{d:"M3 16h6.857c.162-.012.19-.323.038-.38a6 6 0 1 1 4.212 0c-.153.057-.125.368.038.38H21"}],["path",{d:"M3 20h18"}]],HS=[["path",{d:"M19 21a15 15 0 0 1 0-18"}],["path",{d:"M20 12H4"}],["path",{d:"M5 3a15 15 0 0 1 0 18"}]],VS=[["path",{d:"M15 3h6v6"}],["path",{d:"M21 3 3 21"}],["path",{d:"m9 9 6 6"}]],SS=[["path",{d:"M10 19V5.5a1 1 0 0 1 5 0V17a2 2 0 0 0 2 2h5l-3-3"}],["path",{d:"m22 19-3 3"}],["path",{d:"M5 19V5.5a1 1 0 0 1 5 0"}],["path",{d:"M5 5.5A2.5 2.5 0 0 0 2.5 3"}]],wS=[["circle",{cx:"12",cy:"15",r:"6"}],["path",{d:"M18 3A6 6 0 0 1 6 3"}]],LS=[["path",{d:"M11 5.5a1 1 0 0 1 5 0V16a5 5 0 0 0 5 5"}],["path",{d:"M16 11.5a1 1 0 0 1 5 0V16a5 5 0 0 1-5 5"}],["path",{d:"M6 19V6a3 3 0 0 0-3-3h0"}],["path",{d:"M6 5.5a1 1 0 0 1 5 0V19"}]],fS=[["circle",{cx:"11",cy:"11",r:"8"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65"}],["line",{x1:"11",x2:"11",y1:"8",y2:"14"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11"}]],kS=[["circle",{cx:"11",cy:"11",r:"8"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11"}]];var PS=Object.freeze({__proto__:null,AArrowDown:Ya,AArrowUp:_a,ALargeSmall:xa,Accessibility:ah,Activity:hh,ActivitySquare:C0,Ad:th,AirVent:dh,Airplay:ch,AlarmCheck:C,AlarmClock:ph,AlarmClockCheck:C,AlarmClockMinus:u,AlarmClockOff:Mh,AlarmClockPlus:g,AlarmMinus:u,AlarmPlus:g,AlarmSmoke:lh,Album:b0,AlertCircle:j,AlertOctagon:X2,AlertTriangle:ka,AlignCenter:ga,AlignCenterHorizontal:ih,AlignCenterVertical:nh,AlignEndHorizontal:eh,AlignEndVertical:rh,AlignHorizontalDistributeCenter:oh,AlignHorizontalDistributeEnd:vh,AlignHorizontalDistributeStart:$h,AlignHorizontalJustifyCenter:mh,AlignHorizontalJustifyEnd:yh,AlignHorizontalJustifyStart:sh,AlignHorizontalSpaceAround:Ch,AlignHorizontalSpaceBetween:uh,AlignJustify:Ha,AlignLeft:s,AlignRight:Aa,AlignStartHorizontal:gh,AlignStartVertical:Hh,AlignVerticalDistributeCenter:Ah,AlignVerticalDistributeEnd:Vh,AlignVerticalDistributeStart:Sh,AlignVerticalJustifyCenter:wh,AlignVerticalJustifyEnd:Lh,AlignVerticalJustifyStart:kh,AlignVerticalSpaceAround:fh,AlignVerticalSpaceBetween:Ph,Ambulance:Bh,Ampersand:zh,Ampersands:Dh,Amphora:Fh,Anchor:Rh,Angle:bh,Angry:Z1,Annoyed:G1,Antenna:Th,Anvil:qh,Aperture:Uh,AppWindow:Gh,AppWindowMac:Oh,Apple:Zh,Archive:Wh,ArchiveRestore:Eh,ArchiveX:Xh,AreaChart:q,Armchair:Ih,ArmenianDram:Nh,ArrowBigDown:Jh,ArrowBigDownDash:Kh,ArrowBigLeft:jh,ArrowBigLeftDash:Qh,ArrowBigRight:_h,ArrowBigRightDash:Yh,ArrowBigUp:at,ArrowBigUpDash:xh,ArrowDown:et,ArrowDown01:ht,ArrowDown10:tt,ArrowDownAZ:A,ArrowDownAz:A,ArrowDownCircle:_,ArrowDownFromLine:dt,ArrowDownLeft:ct,ArrowDownLeftFromCircle:x,ArrowDownLeftFromSquare:V0,ArrowDownLeftSquare:u0,ArrowDownNarrowWide:Mt,ArrowDownRight:pt,ArrowDownRightFromCircle:a1,ArrowDownRightFromSquare:S0,ArrowDownRightSquare:g0,ArrowDownSquare:A0,ArrowDownToDot:lt,ArrowDownToLine:it,ArrowDownUp:nt,ArrowDownWideNarrow:H,ArrowDownZA:V,ArrowDownZa:V,ArrowLeft:$t,ArrowLeftCircle:Y,ArrowLeftFromLine:rt,ArrowLeftRight:vt,ArrowLeftSquare:H0,ArrowLeftToLine:ot,ArrowRight:Ct,ArrowRightCircle:d1,ArrowRightFromLine:mt,ArrowRightLeft:yt,ArrowRightSquare:f0,ArrowRightToLine:st,ArrowUp:kt,ArrowUp01:gt,ArrowUp10:ut,ArrowUpAZ:S,ArrowUpAz:S,ArrowUpCircle:c1,ArrowUpDown:At,ArrowUpFromDot:Ht,ArrowUpFromLine:Vt,ArrowUpLeft:St,ArrowUpLeftFromCircle:h1,ArrowUpLeftFromSquare:w0,ArrowUpLeftSquare:k0,ArrowUpNarrowWide:w,ArrowUpRight:wt,ArrowUpRightFromCircle:t1,ArrowUpRightFromSquare:L0,ArrowUpRightSquare:P0,ArrowUpSquare:B0,ArrowUpToLine:Lt,ArrowUpWideNarrow:ft,ArrowUpZA:L,ArrowUpZa:L,ArrowsUpFromLine:Pt,Asterisk:zt,AsteriskSquare:z0,Astroid:Bt,AtSign:Dt,Atom:Ft,AudioLines:Tt,AudioLinesOff:Rt,AudioLinesX:bt,AudioWaveform:qt,Avocado:Ut,Award:Ot,Axe:Gt,Axis3D:f,Axis3d:f,Baby:Zt,Backpack:Et,Bacon:Wt,Badge:M5,BadgeAlert:It,BadgeCent:Xt,BadgeCheck:k,BadgeDollarSign:Nt,BadgeEuro:Kt,BadgeHelp:P,BadgeIndianRupee:Jt,BadgeInfo:Qt,BadgeJapaneseYen:jt,BadgeMinus:Yt,BadgePercent:_t,BadgePlus:xt,BadgePoundSterling:a5,BadgeQuestionMark:P,BadgeRussianRuble:h5,BadgeSwissFranc:t5,BadgeTurkishLira:d5,BadgeX:c5,BaggageClaim:p5,Balloon:l5,Ban:i5,Banana:n5,Bandage:e5,BangladeshiTaka:r5,Banknote:y5,BanknoteArrowDown:o5,BanknoteArrowUp:v5,BanknoteCheck:$5,BanknoteX:m5,BarChart:X,BarChart2:N,BarChart3:W,BarChart4:E,BarChartBig:Z,BarChartHorizontal:O,BarChartHorizontalBig:U,Barcode:s5,Barrel:C5,Baseline:u5,Bath:g5,Battery:f5,BatteryCharging:A5,BatteryFull:H5,BatteryLow:V5,BatteryMedium:S5,BatteryPlus:w5,BatteryWarning:L5,Beaker:k5,Bean:B5,BeanOff:P5,Bed:F5,BedDouble:z5,BedSingle:D5,Beef:b5,BeefOff:R5,Beer:q5,BeerOff:T5,BelarusianRuble:U5,Bell:N5,BellCheck:O5,BellDot:G5,BellElectric:Z5,BellMinus:E5,BellOff:W5,BellPlus:I5,BellRing:X5,BetweenHorizonalEnd:B,BetweenHorizonalStart:z,BetweenHorizontalEnd:B,BetweenHorizontalStart:z,BetweenVerticalEnd:K5,BetweenVerticalStart:J5,BicepsFlexed:Q5,Bike:j5,Binary:Y5,Binoculars:x5,Biohazard:_5,Bird:a4,Birdhouse:h4,Bitcoin:t4,Blend:d4,Blender:c4,Blinds:M4,Blocks:p4,Bluetooth:e4,BluetoothConnected:l4,BluetoothOff:i4,BluetoothSearching:n4,Bold:r4,Bolt:o4,Bomb:v4,Bone:m4,BoneFracture:$4,Book:O4,BookA:y4,BookAlert:s4,BookAudio:C4,BookBookmark:D,BookCheck:u4,BookCopy:g4,BookDashed:F,BookDown:A4,BookHeadphones:H4,BookHeart:V4,BookImage:S4,BookKey:w4,BookLock:L4,BookMarked:D,BookMinus:f4,BookOpen:B4,BookOpenCheck:k4,BookOpenText:P4,BookPlus:D4,BookSearch:z4,BookTemplate:F,BookText:F4,BookType:R4,BookUp:T4,BookUp2:b4,BookUser:q4,BookX:U4,Bookmark:X4,BookmarkCheck:G4,BookmarkMinus:Z4,BookmarkOff:E4,BookmarkPlus:W4,BookmarkX:I4,BoomBox:N4,Bot:Q4,BotMessageSquare:K4,BotOff:J4,BottleWine:j4,BowArrow:Y4,Box:_4,BoxSelect:X0,Boxes:x4,Braces:R,Brackets:a3,Brain:c3,BrainCircuit:h3,BrainCog:t3,BrainOff:d3,BrickWall:l3,BrickWallFire:M3,BrickWallShield:p3,Bridge:i3,Briefcase:v3,BriefcaseBusiness:n3,BriefcaseConveyorBelt:e3,BriefcaseMedical:r3,BriefcasePlus:o3,BringToFront:$3,Broccoli:m3,Broom:C3,BroomSparkles:y3,Brush:u3,BrushCleaning:s3,Bubbles:g3,Bug:H3,BugOff:A3,BugPlay:V3,Building:w3,Building2:b,BuildingComplex:b,BuildingComplexPlus:S3,Bus:k3,BusFront:L3,Cable:f3,CableCar:P3,Cake:z3,CakeSlice:B3,Calculator:D3,Calendar:hd,Calendar1:F3,CalendarArrowDown:R3,CalendarArrowUp:b3,CalendarCheck:q3,CalendarCheck2:T3,CalendarChevronsLeft:U3,CalendarChevronsRight:O3,CalendarClock:G3,CalendarCog:E3,CalendarDays:Z3,CalendarFold:W3,CalendarHeart:I3,CalendarMinus:N3,CalendarMinus2:X3,CalendarOff:K3,CalendarPlus:_3,CalendarPlus2:J3,CalendarRange:Q3,CalendarSearch:j3,CalendarSync:Y3,CalendarX:ad,CalendarX2:x3,Calendars:td,Camera:dd,CameraOff:cd,Can:pd,CanSoda:Md,CandlestickChart:G,Candy:nd,CandyCane:ld,CandyOff:id,Cannabis:rd,CannabisOff:ed,Captions:T,CaptionsOff:od,Car:yd,CarBattery:vd,CarFront:$d,CarTaxiFront:md,Caravan:sd,CardSim:Cd,Carrot:ud,Carton:Ad,CartonOff:gd,CaseLower:Hd,CaseSensitive:Vd,CaseUpper:Sd,CassetteTape:wd,Cast:Ld,Castle:fd,Cat:kd,Cctv:Bd,CctvOff:Pd,Certificate:zd,ChartArea:q,ChartBar:O,ChartBarBig:U,ChartBarDecreasing:Fd,ChartBarIncreasing:Dd,ChartBarStacked:Rd,ChartCandlestick:G,ChartColumn:W,ChartColumnBig:Z,ChartColumnDecreasing:bd,ChartColumnIncreasing:E,ChartColumnStacked:Td,ChartGantt:qd,ChartLine:I,ChartNetwork:Ud,ChartNoAxesColumn:N,ChartNoAxesColumnDecreasing:Od,ChartNoAxesColumnIncreasing:X,ChartNoAxesCombined:Gd,ChartNoAxesGantt:K,ChartPie:J,ChartScatter:Q,ChartSpline:Zd,Check:Id,CheckCheck:Ed,CheckCircle:M1,CheckCircle2:p1,CheckLine:Wd,CheckSquare:T0,CheckSquare2:q0,ChefHat:Xd,Cherry:Nd,ChessBishop:Jd,ChessKing:Kd,ChessKnight:Qd,ChessPawn:jd,ChessQueen:Yd,ChessRook:_d,ChevronDown:xd,ChevronDownCircle:l1,ChevronDownSquare:O0,ChevronFirst:a6,ChevronLast:h6,ChevronLeft:t6,ChevronLeftCircle:i1,ChevronLeftSquare:Z0,ChevronRight:d6,ChevronRightCircle:n1,ChevronRightSquare:U0,ChevronUp:c6,ChevronUpCircle:e1,ChevronUpSquare:G0,ChevronsDown:p6,ChevronsDownUp:M6,ChevronsLeft:n6,ChevronsLeftRight:i6,ChevronsLeftRightEllipsis:l6,ChevronsRight:r6,ChevronsRightLeft:e6,ChevronsUp:$6,ChevronsUpDown:o6,Church:v6,Cigarette:y6,CigaretteOff:m6,Circle:U6,CircleAlert:j,CircleArrowDown:_,CircleArrowLeft:Y,CircleArrowOutDownLeft:x,CircleArrowOutDownRight:a1,CircleArrowOutUpLeft:h1,CircleArrowOutUpRight:t1,CircleArrowRight:d1,CircleArrowUp:c1,CircleCheck:p1,CircleCheckBig:M1,CircleChevronDown:l1,CircleChevronLeft:i1,CircleChevronRight:n1,CircleChevronUp:e1,CircleChevronsDown:s6,CircleChevronsLeft:C6,CircleChevronsRight:u6,CircleChevronsUp:g6,CircleDashed:H6,CircleDashedCheck:A6,CircleDivide:r1,CircleDollarSign:V6,CircleDot:w6,CircleDotDashed:S6,CircleEllipsis:L6,CircleEqual:f6,CircleEuro:k6,CircleFadingArrowUp:P6,CircleFadingPlay:B6,CircleFadingPlus:z6,CircleGauge:o1,CircleHelp:n,CircleMinus:v1,CircleOff:D6,CircleParking:m1,CircleParkingOff:$1,CirclePause:y1,CirclePercent:s1,CirclePile:F6,CirclePlay:C1,CirclePlus:u1,CirclePoundSterling:R6,CirclePower:g1,CircleQuestionMark:n,CircleSlash:b6,CircleSlash2:A1,CircleSlashed:A1,CircleSmall:T6,CircleStar:q6,CircleStop:H1,CircleUser:S1,CircleUserRound:V1,CircleX:w1,CircuitBoard:O6,Citrus:G6,Clapperboard:Z6,ClefAlto:E6,ClefBass:W6,ClefTreble:I6,Clipboard:a8,ClipboardCheck:X6,ClipboardClock:N6,ClipboardCopy:K6,ClipboardEdit:f1,ClipboardList:J6,ClipboardMinus:Q6,ClipboardPaste:j6,ClipboardPen:f1,ClipboardPenLine:L1,ClipboardPlus:Y6,ClipboardSignature:L1,ClipboardType:_6,ClipboardX:x6,Clock:A8,Clock1:h8,Clock10:t8,Clock11:d8,Clock12:c8,Clock2:M8,Clock3:p8,Clock4:l8,Clock5:i8,Clock6:n8,Clock7:e8,Clock8:r8,Clock9:o8,ClockAlert:v8,ClockArrowDown:$8,ClockArrowLeft:m8,ClockArrowRight:y8,ClockArrowUp:s8,ClockCheck:C8,ClockFading:u8,ClockPlus:g8,ClosedCaption:H8,Cloud:E8,CloudAlert:V8,CloudBackup:S8,CloudCheck:w8,CloudCog:L8,CloudDownload:k1,CloudDrizzle:f8,CloudFog:k8,CloudHail:P8,CloudLightning:z8,CloudMoon:D8,CloudMoonRain:B8,CloudOff:F8,CloudRain:b8,CloudRainWind:R8,CloudSnow:T8,CloudSun:q8,CloudSunRain:U8,CloudSync:O8,CloudUpload:P1,Cloudy:G8,Clover:Z8,Club:W8,Code:I8,Code2:B1,CodeSquare:E0,CodeXml:B1,Coffee:X8,Cog:N8,Coins:K8,Columns:z1,Columns2:z1,Columns3:D1,Columns3Cog:e,Columns4:J8,ColumnsSettings:e,Combine:Q8,Command:j8,Compass:Y8,Component:_8,Computer:x8,ConciergeBell:hc,Cone:ac,Construction:tc,Contact:dc,Contact2:F1,ContactRound:F1,Container:cc,Contrast:Mc,Cookie:pc,CookingPot:lc,Copy:vc,CopyCheck:ic,CopyMinus:nc,CopyPlus:ec,CopySlash:rc,CopyX:oc,Copyleft:$c,Copyright:mc,CornerDownLeft:yc,CornerDownRight:sc,CornerLeftDown:Cc,CornerLeftUp:uc,CornerRightDown:gc,CornerRightUp:Hc,CornerUpLeft:Ac,CornerUpRight:Vc,Cpu:wc,CpuAi:Sc,CreativeCommons:Lc,CreditCard:Dc,CreditCardCheck:fc,CreditCardMinus:kc,CreditCardPlus:Pc,CreditCardReader:Bc,CreditCardX:zc,Croissant:Fc,Crop:Rc,Cross:bc,Crosshair:Tc,Crown:qc,Cuboid:Uc,CupSoda:Gc,Cupcake:Oc,CurlyBraces:R,Currency:Zc,Cylinder:Ec,Dam:Wc,Database:xc,DatabaseArrowDown:Ic,DatabaseArrowUp:Xc,DatabaseBackup:Nc,DatabaseCheck:Kc,DatabaseMinus:Qc,DatabasePlus:Jc,DatabaseSearch:jc,DatabaseX:Yc,DatabaseZap:_c,DecimalsArrowLeft:a7,DecimalsArrowRight:h7,Delete:d7,Dessert:t7,Diameter:c7,Diamond:l7,DiamondMinus:M7,DiamondPercent:R1,DiamondPlus:p7,Dice1:i7,Dice2:n7,Dice3:e7,Dice4:r7,Dice5:m7,Dice6:o7,Dices:v7,Diff:$7,Disc:u7,Disc2:y7,Disc3:s7,DiscAlbum:C7,Divide:g7,DivideCircle:r1,DivideSquare:N0,Dna:H7,DnaOff:A7,Dock:V7,Dog:S7,DollarSign:w7,Dome:L7,Donut:f7,DoorClosed:z7,DoorClosedCog:k7,DoorClosedLocked:P7,DoorClosedPackage:B7,DoorOpen:D7,DoorStairwell:F7,Dot:R7,DotSquare:J0,Download:b7,DownloadCloud:k1,DraftingCompass:T7,Drama:q7,Drill:U7,Drone:O7,Droplet:Z7,DropletOff:G7,Droplets:E7,Drum:W7,Drumstick:I7,Dumbbell:X7,Ear:K7,EarOff:N7,Earth:b1,EarthLock:J7,Eclipse:Q7,Edit:l,Edit2:c0,Edit3:d0,Egg:_7,EggFried:j7,EggOff:Y7,EighthNote:T1,EighthNotes:q1,Eject:x7,Ellipse:a9,Ellipsis:O1,EllipsisVertical:U1,Engine:h9,Equal:M9,EqualApproximately:d9,EqualApproximatelyNot:t9,EqualNot:c9,EqualSquare:K0,Eraser:i9,EthernetPort:p9,Euro:l9,EvCharger:n9,Expand:e9,ExternalLink:r9,Eye:m9,EyeClosed:o9,EyeDashed:v9,EyeOff:$9,FaceAngry:Z1,FaceExpressionless:G1,FaceGrinning:E1,FaceNeutral:W1,FaceSlightlyFrowning:I1,FaceSlightlySmiling:N1,FaceSlightlySmilingPlus:X1,Factory:y9,Fan:s9,FastForward:C9,Faucet:u9,Feather:A9,Fence:g9,FerrisWheel:H9,File:Y9,FileArchive:V9,FileAudio:r,FileAudio2:r,FileAxis3D:K1,FileAxis3d:K1,FileBadge:J1,FileBadge2:J1,FileBarChart:Y1,FileBarChart2:_1,FileBox:S9,FileBraces:j1,FileBracesCorner:Q1,FileChartColumn:_1,FileChartColumnIncreasing:Y1,FileChartLine:x1,FileChartPie:h2,FileCheck:w9,FileCheck2:a2,FileCheckCorner:a2,FileClock:L9,FileCode:f9,FileCode2:t2,FileCodeCorner:t2,FileCog:d2,FileCog2:d2,FileDiff:k9,FileDigit:P9,FileDown:B9,FileEdit:n2,FileExclamationPoint:c2,FileHeadphone:r,FileHeart:z9,FileImage:D9,FileInput:F9,FileJson:j1,FileJson2:Q1,FileKey:p2,FileKey2:p2,FileLineChart:x1,FileLock:M2,FileLock2:M2,FileMinus:R9,FileMinus2:l2,FileMinusCorner:l2,FileMusic:b9,FileOutput:T9,FilePen:n2,FilePenLine:i2,FilePieChart:h2,FilePlay:e2,FilePlus:q9,FilePlus2:r2,FilePlusCorner:r2,FileQuestion:o2,FileQuestionMark:o2,FileScan:U9,FileSearch:O9,FileSearch2:v2,FileSearchCorner:v2,FileSignal:$2,FileSignature:i2,FileSliders:G9,FileSpreadsheet:Z9,FileStack:E9,FileSymlink:W9,FileTerminal:I9,FileText:X9,FileType:N9,FileType2:m2,FileTypeCorner:m2,FileUp:K9,FileUser:J9,FileVideo:e2,FileVideo2:y2,FileVideoCamera:y2,FileVolume:Q9,FileVolume2:$2,FileWarning:c2,FileX:j9,FileX2:s2,FileXCorner:s2,Files:_9,Film:x9,Filter:H2,FilterX:A2,Fingerprint:C2,FingerprintPattern:C2,FireExtinguisher:aM,Fish:dM,FishOff:hM,FishSymbol:tM,FishingHook:cM,FishingRod:MM,Flag:nM,FlagOff:pM,FlagTriangleLeft:lM,FlagTriangleRight:iM,Flame:rM,FlameKindling:eM,Flashlight:vM,FlashlightOff:oM,FlaskConical:mM,FlaskConicalOff:$M,FlaskRound:yM,FlipHorizontal:F0,FlipHorizontal2:Pa,FlipVertical:R0,FlipVertical2:Ba,Flower:CM,Flower2:sM,Focus:uM,FoldHorizontal:gM,FoldVertical:AM,Folder:_M,FolderArchive:HM,FolderBookmark:VM,FolderCheck:SM,FolderClock:wM,FolderClosed:LM,FolderCode:fM,FolderCog:u2,FolderCog2:u2,FolderDot:kM,FolderDown:PM,FolderEdit:g2,FolderGit:zM,FolderGit2:BM,FolderHeart:DM,FolderInput:FM,FolderKanban:RM,FolderKey:bM,FolderLock:TM,FolderMinus:qM,FolderOpen:OM,FolderOpenDot:UM,FolderOutput:GM,FolderPen:g2,FolderPlus:ZM,FolderRoot:EM,FolderSearch:IM,FolderSearch2:WM,FolderSymlink:XM,FolderSync:NM,FolderTree:KM,FolderUp:JM,FolderUser:QM,FolderUsers:jM,FolderX:YM,Folders:xM,Footprints:ap,ForkKnife:Za,ForkKnifeCrossed:Ga,Forklift:hp,Form:tp,FormInput:p0,Forward:dp,Frame:cp,Frown:I1,Fuel:Mp,Fullscreen:pp,FunctionSquare:Q0,Funnel:H2,FunnelPlus:lp,FunnelX:A2,Galaxy:ip,GalleryHorizontal:np,GalleryHorizontalEnd:ep,GalleryThumbnails:rp,GalleryVertical:vp,GalleryVerticalEnd:op,Gamepad:yp,Gamepad2:$p,GamepadDirectional:mp,GanttChart:K,GanttChartSquare:m,GapHorizontal:sp,GapVertical:Cp,Gauge:up,GaugeCircle:o1,Gavel:gp,Gem:Ap,GeorgianLari:Vp,Germ:Sp,GermOff:Hp,Ghost:wp,Gift:Lp,GitBranch:Pp,GitBranchMinus:fp,GitBranchPlus:kp,GitCommit:V2,GitCommitHorizontal:V2,GitCommitVertical:Bp,GitCompare:Dp,GitCompareArrows:zp,GitFork:Fp,GitGraph:Rp,GitMerge:Tp,GitMergeConflict:bp,GitPullRequest:Ep,GitPullRequestArrow:qp,GitPullRequestClosed:Gp,GitPullRequestCreate:Op,GitPullRequestCreateArrow:Up,GitPullRequestDraft:Zp,GlassWater:Wp,Glasses:Ip,Globe:jp,Globe2:b1,GlobeCheck:Xp,GlobeCode:Np,GlobeLock:Kp,GlobeOff:Jp,GlobeX:Qp,Goal:Yp,Gpu:_p,Grab:P2,GraduationCap:xp,Grape:al,GraphHub:hl,GraphStar:tl,Grid:o,Grid2X2:f2,Grid2X2Check:S2,Grid2X2Plus:w2,Grid2X2X:L2,Grid2x2:f2,Grid2x2Check:S2,Grid2x2Plus:w2,Grid2x2X:L2,Grid3X3:o,Grid3x2:dl,Grid3x3:o,Grip:pl,GripHorizontal:cl,GripVertical:Ml,Groceries:ll,Group:il,Guitar:nl,GuitarElectric:el,HalfNote:k2,Ham:rl,Hamburger:ol,Hammer:vl,Hand:ul,HandCoins:$l,HandFist:ml,HandGrab:P2,HandHeart:yl,HandHelping:B2,HandMetal:sl,HandPlatter:Cl,Handbag:gl,HandleBottomRight:Al,Handshake:Hl,HardDrive:wl,HardDriveDownload:Vl,HardDriveUpload:Sl,HardHat:Ll,Hash:fl,HatGlasses:kl,Haze:Bl,Hd:Pl,HdmiPort:zl,Heading:Ul,Heading1:Dl,Heading2:Fl,Heading3:Rl,Heading4:bl,Heading5:Tl,Heading6:ql,HeadphoneOff:Ol,Headphones:Gl,Headset:Zl,Heart:Ql,HeartCrack:El,HeartHandshake:Wl,HeartMinus:Il,HeartOff:Xl,HeartPlus:Nl,HeartPulse:Kl,HeartX:Jl,Heater:jl,Helicopter:Yl,HelpCircle:n,HelpingHand:B2,Hexagon:_l,Highlighter:xl,HikingStick:ai,History:i0,Home:z2,Hop:hi,HopOff:ti,Hospital:di,Hotel:ci,Hourglass:pi,HourglassCog:Mi,House:z2,HouseCog:li,HouseHeart:ii,HousePlug:ei,HousePlus:ni,HouseWifi:ri,Houses:oi,IceCream:F2,IceCream2:D2,IceCreamBowl:D2,IceCreamCone:F2,IdCard:si,IdCardLanyard:vi,Image:Hi,ImageDown:$i,ImageMinus:mi,ImageOff:yi,ImagePlay:Ci,ImagePlus:ui,ImageUp:gi,ImageUpscale:Ai,Images:Vi,Import:Si,Inbox:fi,Indent:$,IndentDecrease:v,IndentIncrease:$,IndianRupee:wi,Infinity:Li,Info:ki,Inspect:ha,InspectionPanel:Pi,Italic:Bi,IterationCcw:zi,IterationCw:Fi,IvBag:Di,JapaneseYen:Ri,Joystick:bi,Kanban:qi,KanbanSquare:j0,KanbanSquareDashed:I0,Kayak:Ti,KazakhTenge:Ui,Key:Zi,KeyRound:Oi,KeySquare:Gi,Keyboard:Wi,KeyboardMusic:Ei,KeyboardOff:Ii,Lambda:Xi,Lamp:Yi,LampCeiling:Ni,LampDesk:Ki,LampFloor:Ji,LampWallDown:Qi,LampWallUp:ji,LandPlot:_i,Landmark:xi,Languages:an,Laptop:tn,Laptop2:R2,LaptopMinimal:R2,LaptopMinimalCheck:hn,Lasso:Mn,LassoSelect:dn,Laugh:E1,LayerArrowDown:cn,LayerArrowUp:pn,Layers:b2,Layers2:ln,Layers3:b2,LayersArrowDown:nn,LayersArrowUp:en,LayersMinus:rn,LayersPlus:on,Layout:t0,LayoutArrowDown:vn,LayoutArrowRight:$n,LayoutDashboard:mn,LayoutFreeform:yn,LayoutGrid:un,LayoutGridCircles:sn,LayoutList:Cn,LayoutPanelLeft:gn,LayoutPanelTop:An,LayoutTemplate:Hn,Leaf:Vn,LeafyGreen:Sn,Lectern:wn,Lens:kn,LensConcave:Ln,LensConvex:fn,LetterSpacing:Pn,LetterText:Va,Letters:Bn,Library:Dn,LibraryBig:zn,LibrarySquare:Y0,LifeBuoy:Rn,Ligature:Fn,Lightbulb:Tn,LightbulbOff:bn,Lighthouse:qn,LineChart:I,LineDotBottomVertical:Un,LineDotLeftHorizontal:On,LineDotRightHorizontal:Gn,LineDotTopVertical:Zn,LineHeight:En,LineSquiggle:Wn,LineStyle:In,Link:Kn,Link2:Nn,Link2Off:Xn,List:me,ListCheck:Jn,ListChecks:Qn,ListChevronsDownUp:jn,ListChevronsUpDown:Yn,ListClock:_n,ListCollapse:xn,ListEnd:ae,ListFilter:te,ListFilterPlus:he,ListIndentDecrease:v,ListIndentIncrease:$,ListMinus:de,ListMusic:ce,ListOrdered:Me,ListPlus:pe,ListRestart:le,ListSortAscending:ie,ListSortDescending:ee,ListStart:re,ListTodo:ne,ListTree:oe,ListVideo:ve,ListX:$e,Loader:se,Loader2:T2,LoaderCircle:T2,LoaderPinwheel:ye,Locate:ue,LocateFixed:Ce,LocateOff:ge,LocationEdit:G2,Lock:He,LockKeyhole:Ae,LockKeyholeOpen:q2,LockOpen:U2,LogIn:Ve,LogOut:we,Logs:Se,Lollipop:Le,Luggage:fe,MSquare:_0,Magnet:ke,Mail:Ge,MailBadge:Pe,MailCheck:Be,MailClock:ze,MailDot:De,MailMinus:Fe,MailOpen:Re,MailPen:be,MailPlus:Te,MailQuestion:O2,MailQuestionMark:O2,MailSearch:qe,MailWarning:Ue,MailX:Oe,Mailbox:Ie,Mails:Ze,Map:cr,MapMinus:Ee,MapPin:dr,MapPinCheck:We,MapPinCheckInside:Xe,MapPinHouse:Ne,MapPinMinus:Je,MapPinMinusInside:Ke,MapPinOff:je,MapPinPen:G2,MapPinPlus:_e,MapPinPlusInside:Qe,MapPinSearch:Ye,MapPinX:ar,MapPinXInside:xe,MapPinned:hr,MapPlus:tr,Mars:pr,MarsStroke:Mr,Martini:nr,Maximize:ir,Maximize2:lr,Medal:er,Megaphone:or,MegaphoneOff:rr,Meh:W1,MemoryStick:vr,Menu:$r,MenuSquare:x0,Merge:mr,MessageCircle:kr,MessageCircleCheck:yr,MessageCircleCode:sr,MessageCircleDashed:ur,MessageCircleDashedCheck:Cr,MessageCircleHeart:gr,MessageCircleMore:Ar,MessageCircleOff:Hr,MessageCirclePlus:Vr,MessageCircleQuestion:Z2,MessageCircleQuestionMark:Z2,MessageCircleReply:wr,MessageCircleText:Sr,MessageCircleWarning:Lr,MessageCircleX:fr,MessageSquare:Xr,MessageSquareCheck:Pr,MessageSquareCode:zr,MessageSquareDashed:Br,MessageSquareDiff:Dr,MessageSquareDot:Fr,MessageSquareHeart:Rr,MessageSquareLock:br,MessageSquareMore:Tr,MessageSquareOff:qr,MessageSquarePlus:Ur,MessageSquareQuote:Zr,MessageSquareReply:Gr,MessageSquareShare:Or,MessageSquareText:Er,MessageSquareWarning:Wr,MessageSquareX:Ir,MessagesCircle:Nr,MessagesSquare:Kr,Metronome:Jr,Mic:Yr,Mic2:W2,MicAudioLines:Qr,MicOff:jr,MicSignal:E2,MicVocal:W2,Microchip:_r,Microscope:xr,Microwave:ao,MidiPort:ho,Milestone:to,Milk:po,MilkOff:co,Minimize:lo,Minimize2:Mo,Minus:io,MinusCircle:v1,MinusSquare:aa,MirrorRectangular:no,MirrorRound:eo,Monitor:wo,MonitorCheck:oo,MonitorCloud:ro,MonitorCog:vo,MonitorDot:$o,MonitorDown:mo,MonitorOff:yo,MonitorPause:so,MonitorPc:Co,MonitorPlay:uo,MonitorSmartphone:go,MonitorSpeaker:Ao,MonitorStop:Ho,MonitorUp:Vo,MonitorX:So,Moon:fo,MoonStar:Lo,Mop:Po,MopSparkles:ko,MoreHorizontal:O1,MoreVertical:U1,Mosque:Bo,Motorbike:zo,Mountain:Fo,MountainSnow:Do,Mouse:Eo,MouseLeft:Ro,MouseOff:bo,MousePointer:Go,MousePointer2:qo,MousePointer2Off:To,MousePointerBan:Uo,MousePointerClick:Oo,MousePointerSquareDashed:W0,MouseRight:Zo,Mouth:Io,MouthOff:Wo,Move:tv,Move3D:I2,Move3d:I2,MoveDiagonal:No,MoveDiagonal2:Xo,MoveDown:Qo,MoveDownLeft:Ko,MoveDownRight:Jo,MoveHorizontal:jo,MoveLeft:Yo,MoveRight:_o,MoveUp:hv,MoveUpLeft:xo,MoveUpRight:av,MoveVertical:dv,Music:q1,Music2:T1,Music3:k2,Music4:m0,Navigation:lv,Navigation2:Mv,Navigation2Off:cv,NavigationOff:pv,NepaliRupee:iv,Network:nv,Newspaper:ev,Nfc:rv,NonBinary:ov,Notebook:sv,NotebookDot:vv,NotebookPen:$v,NotebookTabs:mv,NotebookText:yv,NotepadText:uv,NotepadTextDashed:Cv,Nut:Av,NutOff:gv,Octagon:Vv,OctagonAlert:X2,OctagonMinus:Hv,OctagonPause:N2,OctagonX:K2,Omega:Sv,Option:wv,Orbit:Lv,Origami:fv,Outdent:v,Package:bv,Package2:kv,PackageCheck:Bv,PackageMinus:Pv,PackageOpen:zv,PackagePlus:Dv,PackageSearch:Fv,PackageX:Rv,PaintBucket:Tv,PaintRoller:qv,Paintbrush:Uv,Paintbrush2:J2,PaintbrushVertical:J2,Palette:Ov,Palmtree:fa,Panda:Gv,PanelBottom:Wv,PanelBottomClose:Zv,PanelBottomDashed:Q2,PanelBottomInactive:Q2,PanelBottomOpen:Ev,PanelLeft:x2,PanelLeftClose:j2,PanelLeftDashed:Y2,PanelLeftInactive:Y2,PanelLeftOpen:_2,PanelLeftRightDashed:Iv,PanelRight:Jv,PanelRightClose:Xv,PanelRightDashed:a0,PanelRightInactive:a0,PanelRightOpen:Nv,PanelTop:Yv,PanelTopBottomDashed:Kv,PanelTopClose:Qv,PanelTopDashed:h0,PanelTopInactive:h0,PanelTopOpen:jv,PanelsLeftBottom:_v,PanelsLeftRight:D1,PanelsRightBottom:xv,PanelsTopBottom:e0,PanelsTopLeft:t0,PaperBag:a$,Paperclip:h$,Parasol:t$,Parentheses:d$,Park:c$,ParkingCircle:m1,ParkingCircleOff:$1,ParkingMeter:M$,ParkingSquare:da,ParkingSquareOff:ta,PartyPopper:p$,Pause:l$,PauseCircle:y1,PauseOctagon:N2,PawPrint:i$,PcCase:n$,Pen:c0,PenBox:l,PenLine:d0,PenOff:e$,PenSquare:l,PenTool:r$,Pencil:y$,PencilLine:o$,PencilOff:v$,PencilRuler:$$,PencilSparkles:m$,Pentagon:s$,Percent:C$,PercentCircle:s1,PercentDiamond:R1,PercentSquare:ca,PerfumeBottle:u$,PersonStanding:A$,Phi:g$,PhilippinePeso:H$,Phone:B$,PhoneCall:V$,PhoneForwarded:w$,PhoneIncoming:S$,PhoneLog:f$,PhoneMissed:L$,PhoneOff:k$,PhoneOutgoing:P$,Pi:z$,PiSquare:Ma,Piano:D$,Pickaxe:F$,PictureInPicture:b$,PictureInPicture2:R$,PieChart:J,PiggyBank:T$,Pilcrow:O$,PilcrowLeft:q$,PilcrowRight:U$,PilcrowSquare:pa,Pill:E$,PillBottle:G$,Pin:W$,PinOff:Z$,Pipette:N$,Pizza:I$,Plane:J$,PlaneLanding:X$,PlaneTakeoff:K$,PlantPot:Q$,Play:Y$,PlayCircle:C1,PlayOff:j$,PlaySquare:la,PlayingCard:_$,PlayingCards:x$,PlayingCardsFan:am,Plug:tm,Plug2:hm,PlugZap:M0,PlugZap2:M0,Plus:dm,PlusCircle:u1,PlusSquare:ia,PocketKnife:cm,Podcast:E2,Podium:Mm,Pointer:lm,PointerOff:pm,Popcorn:im,Popsicle:nm,PoundSterling:em,Power:om,PowerCircle:g1,PowerOff:rm,PowerSquare:na,Presentation:vm,Printer:ym,Printer3d:$m,PrinterCheck:mm,PrinterX:sm,Projector:um,Proportions:Cm,Puzzle:gm,Pyramid:Am,QrCode:Hm,Quote:Vm,Rabbit:Sm,Radar:Lm,Radiation:wm,Radical:fm,Radio:zm,RadioOff:km,RadioReceiver:Pm,RadioTower:Bm,Radius:Dm,Rainbow:Fm,Rat:bm,Ratio:Rm,Receipt:Xm,ReceiptCent:Tm,ReceiptEuro:qm,ReceiptIndianRupee:Um,ReceiptJapaneseYen:Om,ReceiptPoundSterling:Zm,ReceiptRussianRuble:Gm,ReceiptSwissFranc:Em,ReceiptText:Wm,ReceiptTurkishLira:Im,RectangleCircle:Nm,RectangleEllipsis:p0,RectangleGoggles:Km,RectangleHorizontal:Jm,RectangleVertical:Qm,Recycle:jm,Redo:xm,Redo2:Ym,RedoDot:_m,RefreshCcw:hy,RefreshCcwDot:ay,RefreshCw:dy,RefreshCwOff:ty,Refrigerator:cy,Regex:py,RemoveFormatting:My,Repeat:ey,Repeat1:iy,Repeat2:ly,RepeatOff:ny,Replace:oy,ReplaceAll:ry,Reply:$y,ReplyAll:vy,Rewind:my,Ribbon:yy,Road:sy,RobotArm:Cy,RobotVacuum:uy,Rocket:gy,RockingChair:Ay,RollerCoaster:Hy,Rose:Vy,Rotate3D:l0,Rotate3d:l0,RotateCcw:Ly,RotateCcwClock:i0,RotateCcwKey:Sy,RotateCcwSquare:wy,RotateCw:By,RotateCwClock:fy,RotateCwFadingClock:ky,RotateCwSquare:Py,Route:Dy,RouteOff:zy,Router:Fy,Rows:n0,Rows2:n0,Rows3:e0,Rows4:Ry,Rss:by,RugbyBall:Ty,Ruler:Uy,RulerDimensionLine:qy,RussianRuble:Oy,Sailboat:Ey,Salad:Gy,Sandwich:Zy,Satellite:Iy,SatelliteDish:Wy,SaudiRiyal:Xy,Save:Yy,SaveAll:Ny,SaveCheck:Ky,SaveOff:Jy,SavePen:Qy,SavePlus:jy,Scale:_y,Scale3D:r0,Scale3d:r0,Scaling:as,Scan:es,ScanBarcode:xy,ScanBox:hs,ScanEye:ts,ScanFace:ds,ScanHeart:cs,ScanLine:Ms,ScanQrCode:ps,ScanSearch:ls,ScanSquare:is,ScanText:ns,ScatterChart:Q,School:rs,School2:Da,Scissors:vs,ScissorsLineDashed:os,ScissorsSquare:ea,ScissorsSquareDashedBottom:D0,Scooter:$s,ScratchBlocks:ms,ScreenShare:ss,ScreenShareOff:ys,Screw:Cs,Scroll:us,ScrollText:gs,Search:fs,SearchAlert:As,SearchCheck:Hs,SearchCode:Vs,SearchSlash:Ss,SearchX:ws,Section:Ls,Send:Ps,SendHorizonal:o0,SendHorizontal:o0,SendToBack:ks,SeparatorHorizontal:Bs,SeparatorVertical:zs,Server:qs,ServerCog:Ds,ServerCrash:Fs,ServerNas:Rs,ServerOff:bs,ServerPlus:Ts,Settings:Os,Settings2:Us,Shapes:Gs,Share:Zs,Share2:Es,Sheet:Ws,Shell:Is,ShelvingUnit:Xs,Shield:pC,ShieldAlert:Ns,ShieldBan:Ks,ShieldCheck:Js,ShieldClose:$0,ShieldCog:js,ShieldCogCorner:Qs,ShieldEllipsis:Ys,ShieldHalf:_s,ShieldHouse:aC,ShieldKeyhole:xs,ShieldLock:hC,ShieldMinus:tC,ShieldOff:dC,ShieldPlus:cC,ShieldQuestion:v0,ShieldQuestionMark:v0,ShieldUser:MC,ShieldX:$0,Ship:nC,ShipCargo:lC,ShipWheel:iC,Shirt:eC,ShoppingBag:rC,ShoppingBasket:vC,ShoppingCart:mC,ShoppingCartMinus:oC,ShoppingCartPlus:$C,Shovel:yC,ShowerHead:sC,Shredder:CC,Shrimp:gC,ShrimpOff:uC,Shrink:AC,Shrub:HC,Shuffle:VC,Sidebar:x2,SidebarClose:j2,SidebarOpen:_2,Sigma:SC,SigmaSquare:ra,Signal:PC,SignalHigh:wC,SignalLow:LC,SignalMedium:fC,SignalZero:kC,Signature:BC,Signpost:DC,SignpostBig:zC,Siren:FC,SixteenthNotes:m0,SkipBack:RC,SkipForward:bC,Skull:TC,Slash:qC,SlashSquare:oa,Slice:UC,Sliders:y0,SlidersHorizontal:OC,SlidersVertical:y0,Smartphone:WC,SmartphoneCharging:GC,SmartphoneNfc:ZC,SmartphoneUserRound:EC,Smile:N1,SmilePlus:X1,Snail:IC,Snowflake:XC,SoapDispenserDroplet:NC,Sofa:KC,SolarPanel:JC,SortAsc:w,SortDesc:H,Soup:QC,Space:jC,Spade:YC,Sparkle:_C,Sparkles:s0,Speaker:xC,Speech:hu,SpellCheck:tu,SpellCheck2:au,Spline:cu,SplinePointer:du,Split:Mu,SplitSquareHorizontal:va,SplitSquareVertical:$a,Spool:pu,SportShoe:lu,Spotlight:iu,SprayCan:nu,Sprout:eu,Square:zu,SquareActivity:C0,SquareArrowDown:A0,SquareArrowDownLeft:u0,SquareArrowDownRight:g0,SquareArrowLeft:H0,SquareArrowOutDownLeft:V0,SquareArrowOutDownRight:S0,SquareArrowOutUpLeft:w0,SquareArrowOutUpRight:L0,SquareArrowRight:f0,SquareArrowRightEnter:ru,SquareArrowRightExit:ou,SquareArrowUp:B0,SquareArrowUpLeft:k0,SquareArrowUpRight:P0,SquareAsterisk:z0,SquareBookmark:b0,SquareBottomDashedScissors:D0,SquareCenterlineDashedHorizontal:F0,SquareCenterlineDashedVertical:R0,SquareChartGantt:m,SquareCheck:q0,SquareCheckBig:T0,SquareChevronDown:O0,SquareChevronLeft:Z0,SquareChevronRight:U0,SquareChevronUp:G0,SquareCode:E0,SquareDashed:X0,SquareDashedBottom:$u,SquareDashedBottomCode:vu,SquareDashedKanban:I0,SquareDashedMousePointer:W0,SquareDashedPlus:mu,SquareDashedText:y,SquareDashedTopSolid:yu,SquareDashedX:Cu,SquareDashedXCorner:su,SquareDimensions:uu,SquareDivide:N0,SquareDot:J0,SquareEqual:K0,SquareExclamationPoint:gu,SquareFunction:Q0,SquareGanttChart:m,SquareKanban:j0,SquareLibrary:Y0,SquareM:_0,SquareMenu:x0,SquareMinus:aa,SquareMousePointer:ha,SquareOff:Au,SquareParking:da,SquareParkingOff:ta,SquarePause:Hu,SquarePen:l,SquarePercent:ca,SquarePi:Ma,SquarePilcrow:pa,SquarePlay:la,SquarePlus:ia,SquarePower:na,SquareRadical:Vu,SquareRoundCorner:Su,SquareScissors:ea,SquareSigma:ra,SquareSlash:oa,SquareSparkles:wu,SquareSplitHorizontal:va,SquareSplitVertical:$a,SquareSquare:Lu,SquareStack:fu,SquareStar:ku,SquareStop:Pu,SquareTerminal:ya,SquareText:Bu,SquareUser:sa,SquareUserRound:ma,SquareX:Ca,SquaresExclude:Du,SquaresIntersect:Fu,SquaresSubtract:Ru,SquaresUnite:bu,Squircle:Uu,SquircleDashed:Tu,Squirrel:qu,Stamp:Ou,Star:Nu,StarCheck:Gu,StarHalf:Zu,StarMinus:Eu,StarOff:Wu,StarPlus:Iu,StarX:Xu,Stars:s0,StepBack:Ku,StepForward:Ju,Stethoscope:Qu,Sticker:ju,StickyNote:tg,StickyNoteCheck:Yu,StickyNoteMinus:_u,StickyNoteOff:xu,StickyNotePlus:ag,StickyNoteX:hg,StickyNotes:dg,Stone:cg,StopCircle:H1,Store:Mg,StretchHorizontal:pg,StretchVertical:lg,Strikethrough:ig,Subscript:ng,Subtitles:T,Summary:eg,Sun:mg,SunDim:rg,SunMedium:og,SunMoon:vg,SunSnow:$g,Sunrise:yg,Sunset:sg,Superscript:Cg,SwatchBook:ug,SwissFranc:gg,SwitchCamera:Ag,Sword:Hg,Swords:Vg,Syringe:Sg,Table:zg,Table2:wg,TableCellsMerge:Lg,TableCellsSplit:fg,TableColumnsSplit:kg,TableConfig:e,TableOfContents:Pg,TableProperties:Bg,TableRowsSplit:Dg,Tablet:Rg,TabletSmartphone:Fg,Tablets:Tg,Tag:Og,TagPlus:bg,TagX:qg,Tags:Ug,Tally1:Gg,Tally2:Zg,Tally3:Eg,Tally4:Ig,Tally5:Wg,Tangent:Xg,Target:Ng,Telescope:Kg,Tent:jg,TentTree:Jg,Terminal:Qg,TerminalSquare:ya,TestTube:Yg,TestTube2:ua,TestTubeDiagonal:ua,TestTubes:_g,Text:s,TextAlignCenter:ga,TextAlignEnd:Aa,TextAlignJustify:Ha,TextAlignJustifyCenter:xg,TextAlignJustifyEnd:aA,TextAlignJustifyStart:hA,TextAlignStart:s,TextCursor:dA,TextCursorInput:tA,TextInitial:Va,TextQuote:cA,TextSearch:MA,TextSelect:y,TextSelection:y,TextWrap:Sa,Theater:pA,Thermometer:nA,ThermometerSnowflake:lA,ThermometerSun:iA,ThumbsDown:eA,ThumbsUp:rA,TicTacToe:oA,Ticket:uA,TicketCheck:vA,TicketMinus:$A,TicketPercent:mA,TicketPlus:yA,TicketSlash:sA,TicketX:CA,Tickets:HA,TicketsPlane:gA,Timeline:AA,Timer:wA,TimerOff:VA,TimerReset:SA,ToggleLeft:LA,ToggleRight:fA,Toilet:kA,Tombstone:PA,ToolCase:BA,Toolbox:zA,Toothbrush:FA,ToothbrushSparkles:DA,Tornado:RA,Torus:bA,Touchpad:qA,TouchpadOff:TA,TowelRack:OA,TowerControl:UA,ToyBrick:GA,Tractor:ZA,TrafficCone:EA,Trailer:WA,Train:wa,TrainFront:XA,TrainFrontTunnel:IA,TrainTrack:NA,TramFront:wa,Transgender:KA,Trash:La,Trash2:La,TrashOff:JA,TreeDeciduous:QA,TreePalm:fa,TreePine:jA,Trees:YA,TrendingDown:_A,TrendingUp:aH,TrendingUpDown:xA,Triangle:dH,TriangleAlert:ka,TriangleDashed:hH,TriangleRight:tH,TrianglesCenterlineDashedHorizontal:Pa,TrianglesCenterlineDashedVertical:Ba,Trophy:cH,Truck:pH,TruckElectric:MH,TubeLotion:lH,TurkishLira:iH,Turntable:nH,Turtle:eH,Tv:oH,Tv2:za,TvMinimal:za,TvMinimalPlay:rH,Type:sH,TypeOutline:vH,Umbrella:mH,UmbrellaOff:$H,Underline:yH,Undo:gH,Undo2:CH,UndoDot:uH,UnfoldHorizontal:AH,UnfoldVertical:HH,Ungroup:VH,University:Da,Unlink:wH,Unlink2:SH,Unlock:U2,UnlockKeyhole:q2,Unplug:LH,Upload:fH,UploadCloud:P1,Usb:zH,UsbCPort:kH,User:JH,User2:Ua,UserCheck:PH,UserCheck2:Fa,UserCircle:S1,UserCircle2:V1,UserCog:BH,UserCog2:Ra,UserGroup:DH,UserKey:FH,UserLock:RH,UserMinus:bH,UserMinus2:ba,UserPen:qH,UserPlus:TH,UserPlus2:Ta,UserRound:Ua,UserRoundArrowLeft:UH,UserRoundCheck:Fa,UserRoundCog:Ra,UserRoundGroup:OH,UserRoundKey:GH,UserRoundMinus:ba,UserRoundPen:ZH,UserRoundPlus:Ta,UserRoundSearch:EH,UserRoundStar:WH,UserRoundX:qa,UserSearch:IH,UserShield:XH,UserSquare:sa,UserSquare2:ma,UserStar:NH,UserX:KH,UserX2:qa,Users:YH,Users2:Oa,UsersCollaboration:QH,UsersRound:Oa,UsersRoundCollaboration:jH,Utensils:Za,UtensilsCrossed:Ga,UtilityPole:_H,Van:xH,Variable:aV,Vault:hV,VectorPolygon:tV,VectorSquare:dV,Vegan:cV,VenetianMask:MV,Venus:lV,VenusAndMars:pV,Verified:k,Vibrate:nV,VibrateOff:iV,Video:rV,VideoOff:eV,Videotape:oV,View:vV,Virus:mV,VirusOff:$V,Voicemail:sV,Volleyball:yV,Volume:HV,Volume1:CV,Volume2:uV,VolumeOff:gV,VolumeX:AV,Vote:VV,Wallet:wV,Wallet2:Ea,WalletCards:SV,WalletMinimal:Ea,Wallpaper:LV,Wand:fV,Wand2:Wa,WandSparkles:Wa,Warehouse:kV,WashingMachine:PV,Watch:zV,Waves:Ia,WavesArrowDown:BV,WavesArrowUp:DV,WavesHorizontal:Ia,WavesLadder:FV,WavesVertical:RV,Waypoints:bV,Webcam:qV,WebcamOff:TV,Webhook:EV,WebhookOff:UV,Weight:GV,WeightTilde:OV,Wheat:WV,WheatOff:ZV,Whistle:IV,WholeWord:XV,Wifi:aS,WifiCog:NV,WifiHigh:KV,WifiLock:JV,WifiLow:QV,WifiOff:jV,WifiPen:YV,WifiSync:_V,WifiZero:xV,Wind:tS,WindArrowDown:dS,WindArrowUp:hS,Wine:MS,WineOff:cS,Workflow:pS,Worm:lS,WrapText:Sa,Wrench:nS,WrenchOff:iS,X:rS,XCircle:w1,XLineTop:eS,XOctagon:K2,XSquare:Ca,Zap:oS,ZapOff:vS,ZodiacAquarius:$S,ZodiacAries:mS,ZodiacCancer:yS,ZodiacCapricorn:sS,ZodiacGemini:CS,ZodiacLeo:uS,ZodiacLibra:AS,ZodiacOphiuchus:gS,ZodiacPisces:HS,ZodiacSagittarius:VS,ZodiacScorpio:SS,ZodiacTaurus:wS,ZodiacVirgo:LS,ZoomIn:fS,ZoomOut:kS});const BS=({icons:h=PS,nameAttr:t="data-lucide",attrs:c={},root:d=document,inTemplates:M}={})=>{if(!Object.values(h).length)throw new Error(`Please provide an icons object. +If you want to use all the icons you can import it like: + \`import { createIcons, icons } from 'lucide'; +lucide.createIcons({icons});\``);if(typeof d>"u")throw new Error("`createIcons()` only works in a browser environment.");if(Array.from(d.querySelectorAll(`[${t}]`)).forEach(p=>ja(p,{nameAttr:t,icons:h,attrs:c})),M&&Array.from(d.querySelectorAll("template")).forEach(p=>BS({icons:h,nameAttr:t,attrs:c,root:p.content,inTemplates:M})),t==="data-lucide"){const p=d.querySelectorAll("[icon-name]");p.length>0&&(console.warn("[Lucide] Some icons were found with the now deprecated icon-name attribute. These will still be replaced for backwards compatibility, but will no longer be supported in v1.0 and you should switch to data-lucide"),Array.from(p).forEach(Xa=>ja(Xa,{nameAttr:"icon-name",icons:h,attrs:c})))}};a.AArrowDown=Ya,a.AArrowUp=_a,a.ALargeSmall=xa,a.Accessibility=ah,a.Activity=hh,a.ActivitySquare=C0,a.Ad=th,a.AirVent=dh,a.Airplay=ch,a.AlarmCheck=C,a.AlarmClock=ph,a.AlarmClockCheck=C,a.AlarmClockMinus=u,a.AlarmClockOff=Mh,a.AlarmClockPlus=g,a.AlarmMinus=u,a.AlarmPlus=g,a.AlarmSmoke=lh,a.Album=b0,a.AlertCircle=j,a.AlertOctagon=X2,a.AlertTriangle=ka,a.AlignCenter=ga,a.AlignCenterHorizontal=ih,a.AlignCenterVertical=nh,a.AlignEndHorizontal=eh,a.AlignEndVertical=rh,a.AlignHorizontalDistributeCenter=oh,a.AlignHorizontalDistributeEnd=vh,a.AlignHorizontalDistributeStart=$h,a.AlignHorizontalJustifyCenter=mh,a.AlignHorizontalJustifyEnd=yh,a.AlignHorizontalJustifyStart=sh,a.AlignHorizontalSpaceAround=Ch,a.AlignHorizontalSpaceBetween=uh,a.AlignJustify=Ha,a.AlignLeft=s,a.AlignRight=Aa,a.AlignStartHorizontal=gh,a.AlignStartVertical=Hh,a.AlignVerticalDistributeCenter=Ah,a.AlignVerticalDistributeEnd=Vh,a.AlignVerticalDistributeStart=Sh,a.AlignVerticalJustifyCenter=wh,a.AlignVerticalJustifyEnd=Lh,a.AlignVerticalJustifyStart=kh,a.AlignVerticalSpaceAround=fh,a.AlignVerticalSpaceBetween=Ph,a.Ambulance=Bh,a.Ampersand=zh,a.Ampersands=Dh,a.Amphora=Fh,a.Anchor=Rh,a.Angle=bh,a.Angry=Z1,a.Annoyed=G1,a.Antenna=Th,a.Anvil=qh,a.Aperture=Uh,a.AppWindow=Gh,a.AppWindowMac=Oh,a.Apple=Zh,a.Archive=Wh,a.ArchiveRestore=Eh,a.ArchiveX=Xh,a.AreaChart=q,a.Armchair=Ih,a.ArmenianDram=Nh,a.ArrowBigDown=Jh,a.ArrowBigDownDash=Kh,a.ArrowBigLeft=jh,a.ArrowBigLeftDash=Qh,a.ArrowBigRight=_h,a.ArrowBigRightDash=Yh,a.ArrowBigUp=at,a.ArrowBigUpDash=xh,a.ArrowDown=et,a.ArrowDown01=ht,a.ArrowDown10=tt,a.ArrowDownAZ=A,a.ArrowDownAz=A,a.ArrowDownCircle=_,a.ArrowDownFromLine=dt,a.ArrowDownLeft=ct,a.ArrowDownLeftFromCircle=x,a.ArrowDownLeftFromSquare=V0,a.ArrowDownLeftSquare=u0,a.ArrowDownNarrowWide=Mt,a.ArrowDownRight=pt,a.ArrowDownRightFromCircle=a1,a.ArrowDownRightFromSquare=S0,a.ArrowDownRightSquare=g0,a.ArrowDownSquare=A0,a.ArrowDownToDot=lt,a.ArrowDownToLine=it,a.ArrowDownUp=nt,a.ArrowDownWideNarrow=H,a.ArrowDownZA=V,a.ArrowDownZa=V,a.ArrowLeft=$t,a.ArrowLeftCircle=Y,a.ArrowLeftFromLine=rt,a.ArrowLeftRight=vt,a.ArrowLeftSquare=H0,a.ArrowLeftToLine=ot,a.ArrowRight=Ct,a.ArrowRightCircle=d1,a.ArrowRightFromLine=mt,a.ArrowRightLeft=yt,a.ArrowRightSquare=f0,a.ArrowRightToLine=st,a.ArrowUp=kt,a.ArrowUp01=gt,a.ArrowUp10=ut,a.ArrowUpAZ=S,a.ArrowUpAz=S,a.ArrowUpCircle=c1,a.ArrowUpDown=At,a.ArrowUpFromDot=Ht,a.ArrowUpFromLine=Vt,a.ArrowUpLeft=St,a.ArrowUpLeftFromCircle=h1,a.ArrowUpLeftFromSquare=w0,a.ArrowUpLeftSquare=k0,a.ArrowUpNarrowWide=w,a.ArrowUpRight=wt,a.ArrowUpRightFromCircle=t1,a.ArrowUpRightFromSquare=L0,a.ArrowUpRightSquare=P0,a.ArrowUpSquare=B0,a.ArrowUpToLine=Lt,a.ArrowUpWideNarrow=ft,a.ArrowUpZA=L,a.ArrowUpZa=L,a.ArrowsUpFromLine=Pt,a.Asterisk=zt,a.AsteriskSquare=z0,a.Astroid=Bt,a.AtSign=Dt,a.Atom=Ft,a.AudioLines=Tt,a.AudioLinesOff=Rt,a.AudioLinesX=bt,a.AudioWaveform=qt,a.Avocado=Ut,a.Award=Ot,a.Axe=Gt,a.Axis3D=f,a.Axis3d=f,a.Baby=Zt,a.Backpack=Et,a.Bacon=Wt,a.Badge=M5,a.BadgeAlert=It,a.BadgeCent=Xt,a.BadgeCheck=k,a.BadgeDollarSign=Nt,a.BadgeEuro=Kt,a.BadgeHelp=P,a.BadgeIndianRupee=Jt,a.BadgeInfo=Qt,a.BadgeJapaneseYen=jt,a.BadgeMinus=Yt,a.BadgePercent=_t,a.BadgePlus=xt,a.BadgePoundSterling=a5,a.BadgeQuestionMark=P,a.BadgeRussianRuble=h5,a.BadgeSwissFranc=t5,a.BadgeTurkishLira=d5,a.BadgeX=c5,a.BaggageClaim=p5,a.Balloon=l5,a.Ban=i5,a.Banana=n5,a.Bandage=e5,a.BangladeshiTaka=r5,a.Banknote=y5,a.BanknoteArrowDown=o5,a.BanknoteArrowUp=v5,a.BanknoteCheck=$5,a.BanknoteX=m5,a.BarChart=X,a.BarChart2=N,a.BarChart3=W,a.BarChart4=E,a.BarChartBig=Z,a.BarChartHorizontal=O,a.BarChartHorizontalBig=U,a.Barcode=s5,a.Barrel=C5,a.Baseline=u5,a.Bath=g5,a.Battery=f5,a.BatteryCharging=A5,a.BatteryFull=H5,a.BatteryLow=V5,a.BatteryMedium=S5,a.BatteryPlus=w5,a.BatteryWarning=L5,a.Beaker=k5,a.Bean=B5,a.BeanOff=P5,a.Bed=F5,a.BedDouble=z5,a.BedSingle=D5,a.Beef=b5,a.BeefOff=R5,a.Beer=q5,a.BeerOff=T5,a.BelarusianRuble=U5,a.Bell=N5,a.BellCheck=O5,a.BellDot=G5,a.BellElectric=Z5,a.BellMinus=E5,a.BellOff=W5,a.BellPlus=I5,a.BellRing=X5,a.BetweenHorizonalEnd=B,a.BetweenHorizonalStart=z,a.BetweenHorizontalEnd=B,a.BetweenHorizontalStart=z,a.BetweenVerticalEnd=K5,a.BetweenVerticalStart=J5,a.BicepsFlexed=Q5,a.Bike=j5,a.Binary=Y5,a.Binoculars=x5,a.Biohazard=_5,a.Bird=a4,a.Birdhouse=h4,a.Bitcoin=t4,a.Blend=d4,a.Blender=c4,a.Blinds=M4,a.Blocks=p4,a.Bluetooth=e4,a.BluetoothConnected=l4,a.BluetoothOff=i4,a.BluetoothSearching=n4,a.Bold=r4,a.Bolt=o4,a.Bomb=v4,a.Bone=m4,a.BoneFracture=$4,a.Book=O4,a.BookA=y4,a.BookAlert=s4,a.BookAudio=C4,a.BookBookmark=D,a.BookCheck=u4,a.BookCopy=g4,a.BookDashed=F,a.BookDown=A4,a.BookHeadphones=H4,a.BookHeart=V4,a.BookImage=S4,a.BookKey=w4,a.BookLock=L4,a.BookMarked=D,a.BookMinus=f4,a.BookOpen=B4,a.BookOpenCheck=k4,a.BookOpenText=P4,a.BookPlus=D4,a.BookSearch=z4,a.BookTemplate=F,a.BookText=F4,a.BookType=R4,a.BookUp=T4,a.BookUp2=b4,a.BookUser=q4,a.BookX=U4,a.Bookmark=X4,a.BookmarkCheck=G4,a.BookmarkMinus=Z4,a.BookmarkOff=E4,a.BookmarkPlus=W4,a.BookmarkX=I4,a.BoomBox=N4,a.Bot=Q4,a.BotMessageSquare=K4,a.BotOff=J4,a.BottleWine=j4,a.BowArrow=Y4,a.Box=_4,a.BoxSelect=X0,a.Boxes=x4,a.Braces=R,a.Brackets=a3,a.Brain=c3,a.BrainCircuit=h3,a.BrainCog=t3,a.BrainOff=d3,a.BrickWall=l3,a.BrickWallFire=M3,a.BrickWallShield=p3,a.Bridge=i3,a.Briefcase=v3,a.BriefcaseBusiness=n3,a.BriefcaseConveyorBelt=e3,a.BriefcaseMedical=r3,a.BriefcasePlus=o3,a.BringToFront=$3,a.Broccoli=m3,a.Broom=C3,a.BroomSparkles=y3,a.Brush=u3,a.BrushCleaning=s3,a.Bubbles=g3,a.Bug=H3,a.BugOff=A3,a.BugPlay=V3,a.Building=w3,a.Building2=b,a.BuildingComplex=b,a.BuildingComplexPlus=S3,a.Bus=k3,a.BusFront=L3,a.Cable=f3,a.CableCar=P3,a.Cake=z3,a.CakeSlice=B3,a.Calculator=D3,a.Calendar=hd,a.Calendar1=F3,a.CalendarArrowDown=R3,a.CalendarArrowUp=b3,a.CalendarCheck=q3,a.CalendarCheck2=T3,a.CalendarChevronsLeft=U3,a.CalendarChevronsRight=O3,a.CalendarClock=G3,a.CalendarCog=E3,a.CalendarDays=Z3,a.CalendarFold=W3,a.CalendarHeart=I3,a.CalendarMinus=N3,a.CalendarMinus2=X3,a.CalendarOff=K3,a.CalendarPlus=_3,a.CalendarPlus2=J3,a.CalendarRange=Q3,a.CalendarSearch=j3,a.CalendarSync=Y3,a.CalendarX=ad,a.CalendarX2=x3,a.Calendars=td,a.Camera=dd,a.CameraOff=cd,a.Can=pd,a.CanSoda=Md,a.CandlestickChart=G,a.Candy=nd,a.CandyCane=ld,a.CandyOff=id,a.Cannabis=rd,a.CannabisOff=ed,a.Captions=T,a.CaptionsOff=od,a.Car=yd,a.CarBattery=vd,a.CarFront=$d,a.CarTaxiFront=md,a.Caravan=sd,a.CardSim=Cd,a.Carrot=ud,a.Carton=Ad,a.CartonOff=gd,a.CaseLower=Hd,a.CaseSensitive=Vd,a.CaseUpper=Sd,a.CassetteTape=wd,a.Cast=Ld,a.Castle=fd,a.Cat=kd,a.Cctv=Bd,a.CctvOff=Pd,a.Certificate=zd,a.ChartArea=q,a.ChartBar=O,a.ChartBarBig=U,a.ChartBarDecreasing=Fd,a.ChartBarIncreasing=Dd,a.ChartBarStacked=Rd,a.ChartCandlestick=G,a.ChartColumn=W,a.ChartColumnBig=Z,a.ChartColumnDecreasing=bd,a.ChartColumnIncreasing=E,a.ChartColumnStacked=Td,a.ChartGantt=qd,a.ChartLine=I,a.ChartNetwork=Ud,a.ChartNoAxesColumn=N,a.ChartNoAxesColumnDecreasing=Od,a.ChartNoAxesColumnIncreasing=X,a.ChartNoAxesCombined=Gd,a.ChartNoAxesGantt=K,a.ChartPie=J,a.ChartScatter=Q,a.ChartSpline=Zd,a.Check=Id,a.CheckCheck=Ed,a.CheckCircle=M1,a.CheckCircle2=p1,a.CheckLine=Wd,a.CheckSquare=T0,a.CheckSquare2=q0,a.ChefHat=Xd,a.Cherry=Nd,a.ChessBishop=Jd,a.ChessKing=Kd,a.ChessKnight=Qd,a.ChessPawn=jd,a.ChessQueen=Yd,a.ChessRook=_d,a.ChevronDown=xd,a.ChevronDownCircle=l1,a.ChevronDownSquare=O0,a.ChevronFirst=a6,a.ChevronLast=h6,a.ChevronLeft=t6,a.ChevronLeftCircle=i1,a.ChevronLeftSquare=Z0,a.ChevronRight=d6,a.ChevronRightCircle=n1,a.ChevronRightSquare=U0,a.ChevronUp=c6,a.ChevronUpCircle=e1,a.ChevronUpSquare=G0,a.ChevronsDown=p6,a.ChevronsDownUp=M6,a.ChevronsLeft=n6,a.ChevronsLeftRight=i6,a.ChevronsLeftRightEllipsis=l6,a.ChevronsRight=r6,a.ChevronsRightLeft=e6,a.ChevronsUp=$6,a.ChevronsUpDown=o6,a.Church=v6,a.Cigarette=y6,a.CigaretteOff=m6,a.Circle=U6,a.CircleAlert=j,a.CircleArrowDown=_,a.CircleArrowLeft=Y,a.CircleArrowOutDownLeft=x,a.CircleArrowOutDownRight=a1,a.CircleArrowOutUpLeft=h1,a.CircleArrowOutUpRight=t1,a.CircleArrowRight=d1,a.CircleArrowUp=c1,a.CircleCheck=p1,a.CircleCheckBig=M1,a.CircleChevronDown=l1,a.CircleChevronLeft=i1,a.CircleChevronRight=n1,a.CircleChevronUp=e1,a.CircleChevronsDown=s6,a.CircleChevronsLeft=C6,a.CircleChevronsRight=u6,a.CircleChevronsUp=g6,a.CircleDashed=H6,a.CircleDashedCheck=A6,a.CircleDivide=r1,a.CircleDollarSign=V6,a.CircleDot=w6,a.CircleDotDashed=S6,a.CircleEllipsis=L6,a.CircleEqual=f6,a.CircleEuro=k6,a.CircleFadingArrowUp=P6,a.CircleFadingPlay=B6,a.CircleFadingPlus=z6,a.CircleGauge=o1,a.CircleHelp=n,a.CircleMinus=v1,a.CircleOff=D6,a.CircleParking=m1,a.CircleParkingOff=$1,a.CirclePause=y1,a.CirclePercent=s1,a.CirclePile=F6,a.CirclePlay=C1,a.CirclePlus=u1,a.CirclePoundSterling=R6,a.CirclePower=g1,a.CircleQuestionMark=n,a.CircleSlash=b6,a.CircleSlash2=A1,a.CircleSlashed=A1,a.CircleSmall=T6,a.CircleStar=q6,a.CircleStop=H1,a.CircleUser=S1,a.CircleUserRound=V1,a.CircleX=w1,a.CircuitBoard=O6,a.Citrus=G6,a.Clapperboard=Z6,a.ClefAlto=E6,a.ClefBass=W6,a.ClefTreble=I6,a.Clipboard=a8,a.ClipboardCheck=X6,a.ClipboardClock=N6,a.ClipboardCopy=K6,a.ClipboardEdit=f1,a.ClipboardList=J6,a.ClipboardMinus=Q6,a.ClipboardPaste=j6,a.ClipboardPen=f1,a.ClipboardPenLine=L1,a.ClipboardPlus=Y6,a.ClipboardSignature=L1,a.ClipboardType=_6,a.ClipboardX=x6,a.Clock=A8,a.Clock1=h8,a.Clock10=t8,a.Clock11=d8,a.Clock12=c8,a.Clock2=M8,a.Clock3=p8,a.Clock4=l8,a.Clock5=i8,a.Clock6=n8,a.Clock7=e8,a.Clock8=r8,a.Clock9=o8,a.ClockAlert=v8,a.ClockArrowDown=$8,a.ClockArrowLeft=m8,a.ClockArrowRight=y8,a.ClockArrowUp=s8,a.ClockCheck=C8,a.ClockFading=u8,a.ClockPlus=g8,a.ClosedCaption=H8,a.Cloud=E8,a.CloudAlert=V8,a.CloudBackup=S8,a.CloudCheck=w8,a.CloudCog=L8,a.CloudDownload=k1,a.CloudDrizzle=f8,a.CloudFog=k8,a.CloudHail=P8,a.CloudLightning=z8,a.CloudMoon=D8,a.CloudMoonRain=B8,a.CloudOff=F8,a.CloudRain=b8,a.CloudRainWind=R8,a.CloudSnow=T8,a.CloudSun=q8,a.CloudSunRain=U8,a.CloudSync=O8,a.CloudUpload=P1,a.Cloudy=G8,a.Clover=Z8,a.Club=W8,a.Code=I8,a.Code2=B1,a.CodeSquare=E0,a.CodeXml=B1,a.Coffee=X8,a.Cog=N8,a.Coins=K8,a.Columns=z1,a.Columns2=z1,a.Columns3=D1,a.Columns3Cog=e,a.Columns4=J8,a.ColumnsSettings=e,a.Combine=Q8,a.Command=j8,a.Compass=Y8,a.Component=_8,a.Computer=x8,a.ConciergeBell=hc,a.Cone=ac,a.Construction=tc,a.Contact=dc,a.Contact2=F1,a.ContactRound=F1,a.Container=cc,a.Contrast=Mc,a.Cookie=pc,a.CookingPot=lc,a.Copy=vc,a.CopyCheck=ic,a.CopyMinus=nc,a.CopyPlus=ec,a.CopySlash=rc,a.CopyX=oc,a.Copyleft=$c,a.Copyright=mc,a.CornerDownLeft=yc,a.CornerDownRight=sc,a.CornerLeftDown=Cc,a.CornerLeftUp=uc,a.CornerRightDown=gc,a.CornerRightUp=Hc,a.CornerUpLeft=Ac,a.CornerUpRight=Vc,a.Cpu=wc,a.CpuAi=Sc,a.CreativeCommons=Lc,a.CreditCard=Dc,a.CreditCardCheck=fc,a.CreditCardMinus=kc,a.CreditCardPlus=Pc,a.CreditCardReader=Bc,a.CreditCardX=zc,a.Croissant=Fc,a.Crop=Rc,a.Cross=bc,a.Crosshair=Tc,a.Crown=qc,a.Cuboid=Uc,a.CupSoda=Gc,a.Cupcake=Oc,a.CurlyBraces=R,a.Currency=Zc,a.Cylinder=Ec,a.Dam=Wc,a.Database=xc,a.DatabaseArrowDown=Ic,a.DatabaseArrowUp=Xc,a.DatabaseBackup=Nc,a.DatabaseCheck=Kc,a.DatabaseMinus=Qc,a.DatabasePlus=Jc,a.DatabaseSearch=jc,a.DatabaseX=Yc,a.DatabaseZap=_c,a.DecimalsArrowLeft=a7,a.DecimalsArrowRight=h7,a.Delete=d7,a.Dessert=t7,a.Diameter=c7,a.Diamond=l7,a.DiamondMinus=M7,a.DiamondPercent=R1,a.DiamondPlus=p7,a.Dice1=i7,a.Dice2=n7,a.Dice3=e7,a.Dice4=r7,a.Dice5=m7,a.Dice6=o7,a.Dices=v7,a.Diff=$7,a.Disc=u7,a.Disc2=y7,a.Disc3=s7,a.DiscAlbum=C7,a.Divide=g7,a.DivideCircle=r1,a.DivideSquare=N0,a.Dna=H7,a.DnaOff=A7,a.Dock=V7,a.Dog=S7,a.DollarSign=w7,a.Dome=L7,a.Donut=f7,a.DoorClosed=z7,a.DoorClosedCog=k7,a.DoorClosedLocked=P7,a.DoorClosedPackage=B7,a.DoorOpen=D7,a.DoorStairwell=F7,a.Dot=R7,a.DotSquare=J0,a.Download=b7,a.DownloadCloud=k1,a.DraftingCompass=T7,a.Drama=q7,a.Drill=U7,a.Drone=O7,a.Droplet=Z7,a.DropletOff=G7,a.Droplets=E7,a.Drum=W7,a.Drumstick=I7,a.Dumbbell=X7,a.Ear=K7,a.EarOff=N7,a.Earth=b1,a.EarthLock=J7,a.Eclipse=Q7,a.Edit=l,a.Edit2=c0,a.Edit3=d0,a.Egg=_7,a.EggFried=j7,a.EggOff=Y7,a.EighthNote=T1,a.EighthNotes=q1,a.Eject=x7,a.Ellipse=a9,a.Ellipsis=O1,a.EllipsisVertical=U1,a.Engine=h9,a.Equal=M9,a.EqualApproximately=d9,a.EqualApproximatelyNot=t9,a.EqualNot=c9,a.EqualSquare=K0,a.Eraser=i9,a.EthernetPort=p9,a.Euro=l9,a.EvCharger=n9,a.Expand=e9,a.ExternalLink=r9,a.Eye=m9,a.EyeClosed=o9,a.EyeDashed=v9,a.EyeOff=$9,a.FaceAngry=Z1,a.FaceExpressionless=G1,a.FaceGrinning=E1,a.FaceNeutral=W1,a.FaceSlightlyFrowning=I1,a.FaceSlightlySmiling=N1,a.FaceSlightlySmilingPlus=X1,a.Factory=y9,a.Fan=s9,a.FastForward=C9,a.Faucet=u9,a.Feather=A9,a.Fence=g9,a.FerrisWheel=H9,a.File=Y9,a.FileArchive=V9,a.FileAudio=r,a.FileAudio2=r,a.FileAxis3D=K1,a.FileAxis3d=K1,a.FileBadge=J1,a.FileBadge2=J1,a.FileBarChart=Y1,a.FileBarChart2=_1,a.FileBox=S9,a.FileBraces=j1,a.FileBracesCorner=Q1,a.FileChartColumn=_1,a.FileChartColumnIncreasing=Y1,a.FileChartLine=x1,a.FileChartPie=h2,a.FileCheck=w9,a.FileCheck2=a2,a.FileCheckCorner=a2,a.FileClock=L9,a.FileCode=f9,a.FileCode2=t2,a.FileCodeCorner=t2,a.FileCog=d2,a.FileCog2=d2,a.FileDiff=k9,a.FileDigit=P9,a.FileDown=B9,a.FileEdit=n2,a.FileExclamationPoint=c2,a.FileHeadphone=r,a.FileHeart=z9,a.FileImage=D9,a.FileInput=F9,a.FileJson=j1,a.FileJson2=Q1,a.FileKey=p2,a.FileKey2=p2,a.FileLineChart=x1,a.FileLock=M2,a.FileLock2=M2,a.FileMinus=R9,a.FileMinus2=l2,a.FileMinusCorner=l2,a.FileMusic=b9,a.FileOutput=T9,a.FilePen=n2,a.FilePenLine=i2,a.FilePieChart=h2,a.FilePlay=e2,a.FilePlus=q9,a.FilePlus2=r2,a.FilePlusCorner=r2,a.FileQuestion=o2,a.FileQuestionMark=o2,a.FileScan=U9,a.FileSearch=O9,a.FileSearch2=v2,a.FileSearchCorner=v2,a.FileSignal=$2,a.FileSignature=i2,a.FileSliders=G9,a.FileSpreadsheet=Z9,a.FileStack=E9,a.FileSymlink=W9,a.FileTerminal=I9,a.FileText=X9,a.FileType=N9,a.FileType2=m2,a.FileTypeCorner=m2,a.FileUp=K9,a.FileUser=J9,a.FileVideo=e2,a.FileVideo2=y2,a.FileVideoCamera=y2,a.FileVolume=Q9,a.FileVolume2=$2,a.FileWarning=c2,a.FileX=j9,a.FileX2=s2,a.FileXCorner=s2,a.Files=_9,a.Film=x9,a.Filter=H2,a.FilterX=A2,a.Fingerprint=C2,a.FingerprintPattern=C2,a.FireExtinguisher=aM,a.Fish=dM,a.FishOff=hM,a.FishSymbol=tM,a.FishingHook=cM,a.FishingRod=MM,a.Flag=nM,a.FlagOff=pM,a.FlagTriangleLeft=lM,a.FlagTriangleRight=iM,a.Flame=rM,a.FlameKindling=eM,a.Flashlight=vM,a.FlashlightOff=oM,a.FlaskConical=mM,a.FlaskConicalOff=$M,a.FlaskRound=yM,a.FlipHorizontal=F0,a.FlipHorizontal2=Pa,a.FlipVertical=R0,a.FlipVertical2=Ba,a.Flower=CM,a.Flower2=sM,a.Focus=uM,a.FoldHorizontal=gM,a.FoldVertical=AM,a.Folder=_M,a.FolderArchive=HM,a.FolderBookmark=VM,a.FolderCheck=SM,a.FolderClock=wM,a.FolderClosed=LM,a.FolderCode=fM,a.FolderCog=u2,a.FolderCog2=u2,a.FolderDot=kM,a.FolderDown=PM,a.FolderEdit=g2,a.FolderGit=zM,a.FolderGit2=BM,a.FolderHeart=DM,a.FolderInput=FM,a.FolderKanban=RM,a.FolderKey=bM,a.FolderLock=TM,a.FolderMinus=qM,a.FolderOpen=OM,a.FolderOpenDot=UM,a.FolderOutput=GM,a.FolderPen=g2,a.FolderPlus=ZM,a.FolderRoot=EM,a.FolderSearch=IM,a.FolderSearch2=WM,a.FolderSymlink=XM,a.FolderSync=NM,a.FolderTree=KM,a.FolderUp=JM,a.FolderUser=QM,a.FolderUsers=jM,a.FolderX=YM,a.Folders=xM,a.Footprints=ap,a.ForkKnife=Za,a.ForkKnifeCrossed=Ga,a.Forklift=hp,a.Form=tp,a.FormInput=p0,a.Forward=dp,a.Frame=cp,a.Frown=I1,a.Fuel=Mp,a.Fullscreen=pp,a.FunctionSquare=Q0,a.Funnel=H2,a.FunnelPlus=lp,a.FunnelX=A2,a.Galaxy=ip,a.GalleryHorizontal=np,a.GalleryHorizontalEnd=ep,a.GalleryThumbnails=rp,a.GalleryVertical=vp,a.GalleryVerticalEnd=op,a.Gamepad=yp,a.Gamepad2=$p,a.GamepadDirectional=mp,a.GanttChart=K,a.GanttChartSquare=m,a.GapHorizontal=sp,a.GapVertical=Cp,a.Gauge=up,a.GaugeCircle=o1,a.Gavel=gp,a.Gem=Ap,a.GeorgianLari=Vp,a.Germ=Sp,a.GermOff=Hp,a.Ghost=wp,a.Gift=Lp,a.GitBranch=Pp,a.GitBranchMinus=fp,a.GitBranchPlus=kp,a.GitCommit=V2,a.GitCommitHorizontal=V2,a.GitCommitVertical=Bp,a.GitCompare=Dp,a.GitCompareArrows=zp,a.GitFork=Fp,a.GitGraph=Rp,a.GitMerge=Tp,a.GitMergeConflict=bp,a.GitPullRequest=Ep,a.GitPullRequestArrow=qp,a.GitPullRequestClosed=Gp,a.GitPullRequestCreate=Op,a.GitPullRequestCreateArrow=Up,a.GitPullRequestDraft=Zp,a.GlassWater=Wp,a.Glasses=Ip,a.Globe=jp,a.Globe2=b1,a.GlobeCheck=Xp,a.GlobeCode=Np,a.GlobeLock=Kp,a.GlobeOff=Jp,a.GlobeX=Qp,a.Goal=Yp,a.Gpu=_p,a.Grab=P2,a.GraduationCap=xp,a.Grape=al,a.GraphHub=hl,a.GraphStar=tl,a.Grid=o,a.Grid2X2=f2,a.Grid2X2Check=S2,a.Grid2X2Plus=w2,a.Grid2X2X=L2,a.Grid2x2=f2,a.Grid2x2Check=S2,a.Grid2x2Plus=w2,a.Grid2x2X=L2,a.Grid3X3=o,a.Grid3x2=dl,a.Grid3x3=o,a.Grip=pl,a.GripHorizontal=cl,a.GripVertical=Ml,a.Groceries=ll,a.Group=il,a.Guitar=nl,a.GuitarElectric=el,a.HalfNote=k2,a.Ham=rl,a.Hamburger=ol,a.Hammer=vl,a.Hand=ul,a.HandCoins=$l,a.HandFist=ml,a.HandGrab=P2,a.HandHeart=yl,a.HandHelping=B2,a.HandMetal=sl,a.HandPlatter=Cl,a.Handbag=gl,a.HandleBottomRight=Al,a.Handshake=Hl,a.HardDrive=wl,a.HardDriveDownload=Vl,a.HardDriveUpload=Sl,a.HardHat=Ll,a.Hash=fl,a.HatGlasses=kl,a.Haze=Bl,a.Hd=Pl,a.HdmiPort=zl,a.Heading=Ul,a.Heading1=Dl,a.Heading2=Fl,a.Heading3=Rl,a.Heading4=bl,a.Heading5=Tl,a.Heading6=ql,a.HeadphoneOff=Ol,a.Headphones=Gl,a.Headset=Zl,a.Heart=Ql,a.HeartCrack=El,a.HeartHandshake=Wl,a.HeartMinus=Il,a.HeartOff=Xl,a.HeartPlus=Nl,a.HeartPulse=Kl,a.HeartX=Jl,a.Heater=jl,a.Helicopter=Yl,a.HelpCircle=n,a.HelpingHand=B2,a.Hexagon=_l,a.Highlighter=xl,a.HikingStick=ai,a.History=i0,a.Home=z2,a.Hop=hi,a.HopOff=ti,a.Hospital=di,a.Hotel=ci,a.Hourglass=pi,a.HourglassCog=Mi,a.House=z2,a.HouseCog=li,a.HouseHeart=ii,a.HousePlug=ei,a.HousePlus=ni,a.HouseWifi=ri,a.Houses=oi,a.IceCream=F2,a.IceCream2=D2,a.IceCreamBowl=D2,a.IceCreamCone=F2,a.IdCard=si,a.IdCardLanyard=vi,a.Image=Hi,a.ImageDown=$i,a.ImageMinus=mi,a.ImageOff=yi,a.ImagePlay=Ci,a.ImagePlus=ui,a.ImageUp=gi,a.ImageUpscale=Ai,a.Images=Vi,a.Import=Si,a.Inbox=fi,a.Indent=$,a.IndentDecrease=v,a.IndentIncrease=$,a.IndianRupee=wi,a.Infinity=Li,a.Info=ki,a.Inspect=ha,a.InspectionPanel=Pi,a.Italic=Bi,a.IterationCcw=zi,a.IterationCw=Fi,a.IvBag=Di,a.JapaneseYen=Ri,a.Joystick=bi,a.Kanban=qi,a.KanbanSquare=j0,a.KanbanSquareDashed=I0,a.Kayak=Ti,a.KazakhTenge=Ui,a.Key=Zi,a.KeyRound=Oi,a.KeySquare=Gi,a.Keyboard=Wi,a.KeyboardMusic=Ei,a.KeyboardOff=Ii,a.Lambda=Xi,a.Lamp=Yi,a.LampCeiling=Ni,a.LampDesk=Ki,a.LampFloor=Ji,a.LampWallDown=Qi,a.LampWallUp=ji,a.LandPlot=_i,a.Landmark=xi,a.Languages=an,a.Laptop=tn,a.Laptop2=R2,a.LaptopMinimal=R2,a.LaptopMinimalCheck=hn,a.Lasso=Mn,a.LassoSelect=dn,a.Laugh=E1,a.LayerArrowDown=cn,a.LayerArrowUp=pn,a.Layers=b2,a.Layers2=ln,a.Layers3=b2,a.LayersArrowDown=nn,a.LayersArrowUp=en,a.LayersMinus=rn,a.LayersPlus=on,a.Layout=t0,a.LayoutArrowDown=vn,a.LayoutArrowRight=$n,a.LayoutDashboard=mn,a.LayoutFreeform=yn,a.LayoutGrid=un,a.LayoutGridCircles=sn,a.LayoutList=Cn,a.LayoutPanelLeft=gn,a.LayoutPanelTop=An,a.LayoutTemplate=Hn,a.Leaf=Vn,a.LeafyGreen=Sn,a.Lectern=wn,a.Lens=kn,a.LensConcave=Ln,a.LensConvex=fn,a.LetterSpacing=Pn,a.LetterText=Va,a.Letters=Bn,a.Library=Dn,a.LibraryBig=zn,a.LibrarySquare=Y0,a.LifeBuoy=Rn,a.Ligature=Fn,a.Lightbulb=Tn,a.LightbulbOff=bn,a.Lighthouse=qn,a.LineChart=I,a.LineDotBottomVertical=Un,a.LineDotLeftHorizontal=On,a.LineDotRightHorizontal=Gn,a.LineDotTopVertical=Zn,a.LineHeight=En,a.LineSquiggle=Wn,a.LineStyle=In,a.Link=Kn,a.Link2=Nn,a.Link2Off=Xn,a.List=me,a.ListCheck=Jn,a.ListChecks=Qn,a.ListChevronsDownUp=jn,a.ListChevronsUpDown=Yn,a.ListClock=_n,a.ListCollapse=xn,a.ListEnd=ae,a.ListFilter=te,a.ListFilterPlus=he,a.ListIndentDecrease=v,a.ListIndentIncrease=$,a.ListMinus=de,a.ListMusic=ce,a.ListOrdered=Me,a.ListPlus=pe,a.ListRestart=le,a.ListSortAscending=ie,a.ListSortDescending=ee,a.ListStart=re,a.ListTodo=ne,a.ListTree=oe,a.ListVideo=ve,a.ListX=$e,a.Loader=se,a.Loader2=T2,a.LoaderCircle=T2,a.LoaderPinwheel=ye,a.Locate=ue,a.LocateFixed=Ce,a.LocateOff=ge,a.LocationEdit=G2,a.Lock=He,a.LockKeyhole=Ae,a.LockKeyholeOpen=q2,a.LockOpen=U2,a.LogIn=Ve,a.LogOut=we,a.Logs=Se,a.Lollipop=Le,a.Luggage=fe,a.MSquare=_0,a.Magnet=ke,a.Mail=Ge,a.MailBadge=Pe,a.MailCheck=Be,a.MailClock=ze,a.MailDot=De,a.MailMinus=Fe,a.MailOpen=Re,a.MailPen=be,a.MailPlus=Te,a.MailQuestion=O2,a.MailQuestionMark=O2,a.MailSearch=qe,a.MailWarning=Ue,a.MailX=Oe,a.Mailbox=Ie,a.Mails=Ze,a.Map=cr,a.MapMinus=Ee,a.MapPin=dr,a.MapPinCheck=We,a.MapPinCheckInside=Xe,a.MapPinHouse=Ne,a.MapPinMinus=Je,a.MapPinMinusInside=Ke,a.MapPinOff=je,a.MapPinPen=G2,a.MapPinPlus=_e,a.MapPinPlusInside=Qe,a.MapPinSearch=Ye,a.MapPinX=ar,a.MapPinXInside=xe,a.MapPinned=hr,a.MapPlus=tr,a.Mars=pr,a.MarsStroke=Mr,a.Martini=nr,a.Maximize=ir,a.Maximize2=lr,a.Medal=er,a.Megaphone=or,a.MegaphoneOff=rr,a.Meh=W1,a.MemoryStick=vr,a.Menu=$r,a.MenuSquare=x0,a.Merge=mr,a.MessageCircle=kr,a.MessageCircleCheck=yr,a.MessageCircleCode=sr,a.MessageCircleDashed=ur,a.MessageCircleDashedCheck=Cr,a.MessageCircleHeart=gr,a.MessageCircleMore=Ar,a.MessageCircleOff=Hr,a.MessageCirclePlus=Vr,a.MessageCircleQuestion=Z2,a.MessageCircleQuestionMark=Z2,a.MessageCircleReply=wr,a.MessageCircleText=Sr,a.MessageCircleWarning=Lr,a.MessageCircleX=fr,a.MessageSquare=Xr,a.MessageSquareCheck=Pr,a.MessageSquareCode=zr,a.MessageSquareDashed=Br,a.MessageSquareDiff=Dr,a.MessageSquareDot=Fr,a.MessageSquareHeart=Rr,a.MessageSquareLock=br,a.MessageSquareMore=Tr,a.MessageSquareOff=qr,a.MessageSquarePlus=Ur,a.MessageSquareQuote=Zr,a.MessageSquareReply=Gr,a.MessageSquareShare=Or,a.MessageSquareText=Er,a.MessageSquareWarning=Wr,a.MessageSquareX=Ir,a.MessagesCircle=Nr,a.MessagesSquare=Kr,a.Metronome=Jr,a.Mic=Yr,a.Mic2=W2,a.MicAudioLines=Qr,a.MicOff=jr,a.MicSignal=E2,a.MicVocal=W2,a.Microchip=_r,a.Microscope=xr,a.Microwave=ao,a.MidiPort=ho,a.Milestone=to,a.Milk=po,a.MilkOff=co,a.Minimize=lo,a.Minimize2=Mo,a.Minus=io,a.MinusCircle=v1,a.MinusSquare=aa,a.MirrorRectangular=no,a.MirrorRound=eo,a.Monitor=wo,a.MonitorCheck=oo,a.MonitorCloud=ro,a.MonitorCog=vo,a.MonitorDot=$o,a.MonitorDown=mo,a.MonitorOff=yo,a.MonitorPause=so,a.MonitorPc=Co,a.MonitorPlay=uo,a.MonitorSmartphone=go,a.MonitorSpeaker=Ao,a.MonitorStop=Ho,a.MonitorUp=Vo,a.MonitorX=So,a.Moon=fo,a.MoonStar=Lo,a.Mop=Po,a.MopSparkles=ko,a.MoreHorizontal=O1,a.MoreVertical=U1,a.Mosque=Bo,a.Motorbike=zo,a.Mountain=Fo,a.MountainSnow=Do,a.Mouse=Eo,a.MouseLeft=Ro,a.MouseOff=bo,a.MousePointer=Go,a.MousePointer2=qo,a.MousePointer2Off=To,a.MousePointerBan=Uo,a.MousePointerClick=Oo,a.MousePointerSquareDashed=W0,a.MouseRight=Zo,a.Mouth=Io,a.MouthOff=Wo,a.Move=tv,a.Move3D=I2,a.Move3d=I2,a.MoveDiagonal=No,a.MoveDiagonal2=Xo,a.MoveDown=Qo,a.MoveDownLeft=Ko,a.MoveDownRight=Jo,a.MoveHorizontal=jo,a.MoveLeft=Yo,a.MoveRight=_o,a.MoveUp=hv,a.MoveUpLeft=xo,a.MoveUpRight=av,a.MoveVertical=dv,a.Music=q1,a.Music2=T1,a.Music3=k2,a.Music4=m0,a.Navigation=lv,a.Navigation2=Mv,a.Navigation2Off=cv,a.NavigationOff=pv,a.NepaliRupee=iv,a.Network=nv,a.Newspaper=ev,a.Nfc=rv,a.NonBinary=ov,a.Notebook=sv,a.NotebookDot=vv,a.NotebookPen=$v,a.NotebookTabs=mv,a.NotebookText=yv,a.NotepadText=uv,a.NotepadTextDashed=Cv,a.Nut=Av,a.NutOff=gv,a.Octagon=Vv,a.OctagonAlert=X2,a.OctagonMinus=Hv,a.OctagonPause=N2,a.OctagonX=K2,a.Omega=Sv,a.Option=wv,a.Orbit=Lv,a.Origami=fv,a.Outdent=v,a.Package=bv,a.Package2=kv,a.PackageCheck=Bv,a.PackageMinus=Pv,a.PackageOpen=zv,a.PackagePlus=Dv,a.PackageSearch=Fv,a.PackageX=Rv,a.PaintBucket=Tv,a.PaintRoller=qv,a.Paintbrush=Uv,a.Paintbrush2=J2,a.PaintbrushVertical=J2,a.Palette=Ov,a.Palmtree=fa,a.Panda=Gv,a.PanelBottom=Wv,a.PanelBottomClose=Zv,a.PanelBottomDashed=Q2,a.PanelBottomInactive=Q2,a.PanelBottomOpen=Ev,a.PanelLeft=x2,a.PanelLeftClose=j2,a.PanelLeftDashed=Y2,a.PanelLeftInactive=Y2,a.PanelLeftOpen=_2,a.PanelLeftRightDashed=Iv,a.PanelRight=Jv,a.PanelRightClose=Xv,a.PanelRightDashed=a0,a.PanelRightInactive=a0,a.PanelRightOpen=Nv,a.PanelTop=Yv,a.PanelTopBottomDashed=Kv,a.PanelTopClose=Qv,a.PanelTopDashed=h0,a.PanelTopInactive=h0,a.PanelTopOpen=jv,a.PanelsLeftBottom=_v,a.PanelsLeftRight=D1,a.PanelsRightBottom=xv,a.PanelsTopBottom=e0,a.PanelsTopLeft=t0,a.PaperBag=a$,a.Paperclip=h$,a.Parasol=t$,a.Parentheses=d$,a.Park=c$,a.ParkingCircle=m1,a.ParkingCircleOff=$1,a.ParkingMeter=M$,a.ParkingSquare=da,a.ParkingSquareOff=ta,a.PartyPopper=p$,a.Pause=l$,a.PauseCircle=y1,a.PauseOctagon=N2,a.PawPrint=i$,a.PcCase=n$,a.Pen=c0,a.PenBox=l,a.PenLine=d0,a.PenOff=e$,a.PenSquare=l,a.PenTool=r$,a.Pencil=y$,a.PencilLine=o$,a.PencilOff=v$,a.PencilRuler=$$,a.PencilSparkles=m$,a.Pentagon=s$,a.Percent=C$,a.PercentCircle=s1,a.PercentDiamond=R1,a.PercentSquare=ca,a.PerfumeBottle=u$,a.PersonStanding=A$,a.Phi=g$,a.PhilippinePeso=H$,a.Phone=B$,a.PhoneCall=V$,a.PhoneForwarded=w$,a.PhoneIncoming=S$,a.PhoneLog=f$,a.PhoneMissed=L$,a.PhoneOff=k$,a.PhoneOutgoing=P$,a.Pi=z$,a.PiSquare=Ma,a.Piano=D$,a.Pickaxe=F$,a.PictureInPicture=b$,a.PictureInPicture2=R$,a.PieChart=J,a.PiggyBank=T$,a.Pilcrow=O$,a.PilcrowLeft=q$,a.PilcrowRight=U$,a.PilcrowSquare=pa,a.Pill=E$,a.PillBottle=G$,a.Pin=W$,a.PinOff=Z$,a.Pipette=N$,a.Pizza=I$,a.Plane=J$,a.PlaneLanding=X$,a.PlaneTakeoff=K$,a.PlantPot=Q$,a.Play=Y$,a.PlayCircle=C1,a.PlayOff=j$,a.PlaySquare=la,a.PlayingCard=_$,a.PlayingCards=x$,a.PlayingCardsFan=am,a.Plug=tm,a.Plug2=hm,a.PlugZap=M0,a.PlugZap2=M0,a.Plus=dm,a.PlusCircle=u1,a.PlusSquare=ia,a.PocketKnife=cm,a.Podcast=E2,a.Podium=Mm,a.Pointer=lm,a.PointerOff=pm,a.Popcorn=im,a.Popsicle=nm,a.PoundSterling=em,a.Power=om,a.PowerCircle=g1,a.PowerOff=rm,a.PowerSquare=na,a.Presentation=vm,a.Printer=ym,a.Printer3d=$m,a.PrinterCheck=mm,a.PrinterX=sm,a.Projector=um,a.Proportions=Cm,a.Puzzle=gm,a.Pyramid=Am,a.QrCode=Hm,a.Quote=Vm,a.Rabbit=Sm,a.Radar=Lm,a.Radiation=wm,a.Radical=fm,a.Radio=zm,a.RadioOff=km,a.RadioReceiver=Pm,a.RadioTower=Bm,a.Radius=Dm,a.Rainbow=Fm,a.Rat=bm,a.Ratio=Rm,a.Receipt=Xm,a.ReceiptCent=Tm,a.ReceiptEuro=qm,a.ReceiptIndianRupee=Um,a.ReceiptJapaneseYen=Om,a.ReceiptPoundSterling=Zm,a.ReceiptRussianRuble=Gm,a.ReceiptSwissFranc=Em,a.ReceiptText=Wm,a.ReceiptTurkishLira=Im,a.RectangleCircle=Nm,a.RectangleEllipsis=p0,a.RectangleGoggles=Km,a.RectangleHorizontal=Jm,a.RectangleVertical=Qm,a.Recycle=jm,a.Redo=xm,a.Redo2=Ym,a.RedoDot=_m,a.RefreshCcw=hy,a.RefreshCcwDot=ay,a.RefreshCw=dy,a.RefreshCwOff=ty,a.Refrigerator=cy,a.Regex=py,a.RemoveFormatting=My,a.Repeat=ey,a.Repeat1=iy,a.Repeat2=ly,a.RepeatOff=ny,a.Replace=oy,a.ReplaceAll=ry,a.Reply=$y,a.ReplyAll=vy,a.Rewind=my,a.Ribbon=yy,a.Road=sy,a.RobotArm=Cy,a.RobotVacuum=uy,a.Rocket=gy,a.RockingChair=Ay,a.RollerCoaster=Hy,a.Rose=Vy,a.Rotate3D=l0,a.Rotate3d=l0,a.RotateCcw=Ly,a.RotateCcwClock=i0,a.RotateCcwKey=Sy,a.RotateCcwSquare=wy,a.RotateCw=By,a.RotateCwClock=fy,a.RotateCwFadingClock=ky,a.RotateCwSquare=Py,a.Route=Dy,a.RouteOff=zy,a.Router=Fy,a.Rows=n0,a.Rows2=n0,a.Rows3=e0,a.Rows4=Ry,a.Rss=by,a.RugbyBall=Ty,a.Ruler=Uy,a.RulerDimensionLine=qy,a.RussianRuble=Oy,a.Sailboat=Ey,a.Salad=Gy,a.Sandwich=Zy,a.Satellite=Iy,a.SatelliteDish=Wy,a.SaudiRiyal=Xy,a.Save=Yy,a.SaveAll=Ny,a.SaveCheck=Ky,a.SaveOff=Jy,a.SavePen=Qy,a.SavePlus=jy,a.Scale=_y,a.Scale3D=r0,a.Scale3d=r0,a.Scaling=as,a.Scan=es,a.ScanBarcode=xy,a.ScanBox=hs,a.ScanEye=ts,a.ScanFace=ds,a.ScanHeart=cs,a.ScanLine=Ms,a.ScanQrCode=ps,a.ScanSearch=ls,a.ScanSquare=is,a.ScanText=ns,a.ScatterChart=Q,a.School=rs,a.School2=Da,a.Scissors=vs,a.ScissorsLineDashed=os,a.ScissorsSquare=ea,a.ScissorsSquareDashedBottom=D0,a.Scooter=$s,a.ScratchBlocks=ms,a.ScreenShare=ss,a.ScreenShareOff=ys,a.Screw=Cs,a.Scroll=us,a.ScrollText=gs,a.Search=fs,a.SearchAlert=As,a.SearchCheck=Hs,a.SearchCode=Vs,a.SearchSlash=Ss,a.SearchX=ws,a.Section=Ls,a.Send=Ps,a.SendHorizonal=o0,a.SendHorizontal=o0,a.SendToBack=ks,a.SeparatorHorizontal=Bs,a.SeparatorVertical=zs,a.Server=qs,a.ServerCog=Ds,a.ServerCrash=Fs,a.ServerNas=Rs,a.ServerOff=bs,a.ServerPlus=Ts,a.Settings=Os,a.Settings2=Us,a.Shapes=Gs,a.Share=Zs,a.Share2=Es,a.Sheet=Ws,a.Shell=Is,a.ShelvingUnit=Xs,a.Shield=pC,a.ShieldAlert=Ns,a.ShieldBan=Ks,a.ShieldCheck=Js,a.ShieldClose=$0,a.ShieldCog=js,a.ShieldCogCorner=Qs,a.ShieldEllipsis=Ys,a.ShieldHalf=_s,a.ShieldHouse=aC,a.ShieldKeyhole=xs,a.ShieldLock=hC,a.ShieldMinus=tC,a.ShieldOff=dC,a.ShieldPlus=cC,a.ShieldQuestion=v0,a.ShieldQuestionMark=v0,a.ShieldUser=MC,a.ShieldX=$0,a.Ship=nC,a.ShipCargo=lC,a.ShipWheel=iC,a.Shirt=eC,a.ShoppingBag=rC,a.ShoppingBasket=vC,a.ShoppingCart=mC,a.ShoppingCartMinus=oC,a.ShoppingCartPlus=$C,a.Shovel=yC,a.ShowerHead=sC,a.Shredder=CC,a.Shrimp=gC,a.ShrimpOff=uC,a.Shrink=AC,a.Shrub=HC,a.Shuffle=VC,a.Sidebar=x2,a.SidebarClose=j2,a.SidebarOpen=_2,a.Sigma=SC,a.SigmaSquare=ra,a.Signal=PC,a.SignalHigh=wC,a.SignalLow=LC,a.SignalMedium=fC,a.SignalZero=kC,a.Signature=BC,a.Signpost=DC,a.SignpostBig=zC,a.Siren=FC,a.SixteenthNotes=m0,a.SkipBack=RC,a.SkipForward=bC,a.Skull=TC,a.Slash=qC,a.SlashSquare=oa,a.Slice=UC,a.Sliders=y0,a.SlidersHorizontal=OC,a.SlidersVertical=y0,a.Smartphone=WC,a.SmartphoneCharging=GC,a.SmartphoneNfc=ZC,a.SmartphoneUserRound=EC,a.Smile=N1,a.SmilePlus=X1,a.Snail=IC,a.Snowflake=XC,a.SoapDispenserDroplet=NC,a.Sofa=KC,a.SolarPanel=JC,a.SortAsc=w,a.SortDesc=H,a.Soup=QC,a.Space=jC,a.Spade=YC,a.Sparkle=_C,a.Sparkles=s0,a.Speaker=xC,a.Speech=hu,a.SpellCheck=tu,a.SpellCheck2=au,a.Spline=cu,a.SplinePointer=du,a.Split=Mu,a.SplitSquareHorizontal=va,a.SplitSquareVertical=$a,a.Spool=pu,a.SportShoe=lu,a.Spotlight=iu,a.SprayCan=nu,a.Sprout=eu,a.Square=zu,a.SquareActivity=C0,a.SquareArrowDown=A0,a.SquareArrowDownLeft=u0,a.SquareArrowDownRight=g0,a.SquareArrowLeft=H0,a.SquareArrowOutDownLeft=V0,a.SquareArrowOutDownRight=S0,a.SquareArrowOutUpLeft=w0,a.SquareArrowOutUpRight=L0,a.SquareArrowRight=f0,a.SquareArrowRightEnter=ru,a.SquareArrowRightExit=ou,a.SquareArrowUp=B0,a.SquareArrowUpLeft=k0,a.SquareArrowUpRight=P0,a.SquareAsterisk=z0,a.SquareBookmark=b0,a.SquareBottomDashedScissors=D0,a.SquareCenterlineDashedHorizontal=F0,a.SquareCenterlineDashedVertical=R0,a.SquareChartGantt=m,a.SquareCheck=q0,a.SquareCheckBig=T0,a.SquareChevronDown=O0,a.SquareChevronLeft=Z0,a.SquareChevronRight=U0,a.SquareChevronUp=G0,a.SquareCode=E0,a.SquareDashed=X0,a.SquareDashedBottom=$u,a.SquareDashedBottomCode=vu,a.SquareDashedKanban=I0,a.SquareDashedMousePointer=W0,a.SquareDashedPlus=mu,a.SquareDashedText=y,a.SquareDashedTopSolid=yu,a.SquareDashedX=Cu,a.SquareDashedXCorner=su,a.SquareDimensions=uu,a.SquareDivide=N0,a.SquareDot=J0,a.SquareEqual=K0,a.SquareExclamationPoint=gu,a.SquareFunction=Q0,a.SquareGanttChart=m,a.SquareKanban=j0,a.SquareLibrary=Y0,a.SquareM=_0,a.SquareMenu=x0,a.SquareMinus=aa,a.SquareMousePointer=ha,a.SquareOff=Au,a.SquareParking=da,a.SquareParkingOff=ta,a.SquarePause=Hu,a.SquarePen=l,a.SquarePercent=ca,a.SquarePi=Ma,a.SquarePilcrow=pa,a.SquarePlay=la,a.SquarePlus=ia,a.SquarePower=na,a.SquareRadical=Vu,a.SquareRoundCorner=Su,a.SquareScissors=ea,a.SquareSigma=ra,a.SquareSlash=oa,a.SquareSparkles=wu,a.SquareSplitHorizontal=va,a.SquareSplitVertical=$a,a.SquareSquare=Lu,a.SquareStack=fu,a.SquareStar=ku,a.SquareStop=Pu,a.SquareTerminal=ya,a.SquareText=Bu,a.SquareUser=sa,a.SquareUserRound=ma,a.SquareX=Ca,a.SquaresExclude=Du,a.SquaresIntersect=Fu,a.SquaresSubtract=Ru,a.SquaresUnite=bu,a.Squircle=Uu,a.SquircleDashed=Tu,a.Squirrel=qu,a.Stamp=Ou,a.Star=Nu,a.StarCheck=Gu,a.StarHalf=Zu,a.StarMinus=Eu,a.StarOff=Wu,a.StarPlus=Iu,a.StarX=Xu,a.Stars=s0,a.StepBack=Ku,a.StepForward=Ju,a.Stethoscope=Qu,a.Sticker=ju,a.StickyNote=tg,a.StickyNoteCheck=Yu,a.StickyNoteMinus=_u,a.StickyNoteOff=xu,a.StickyNotePlus=ag,a.StickyNoteX=hg,a.StickyNotes=dg,a.Stone=cg,a.StopCircle=H1,a.Store=Mg,a.StretchHorizontal=pg,a.StretchVertical=lg,a.Strikethrough=ig,a.Subscript=ng,a.Subtitles=T,a.Summary=eg,a.Sun=mg,a.SunDim=rg,a.SunMedium=og,a.SunMoon=vg,a.SunSnow=$g,a.Sunrise=yg,a.Sunset=sg,a.Superscript=Cg,a.SwatchBook=ug,a.SwissFranc=gg,a.SwitchCamera=Ag,a.Sword=Hg,a.Swords=Vg,a.Syringe=Sg,a.Table=zg,a.Table2=wg,a.TableCellsMerge=Lg,a.TableCellsSplit=fg,a.TableColumnsSplit=kg,a.TableConfig=e,a.TableOfContents=Pg,a.TableProperties=Bg,a.TableRowsSplit=Dg,a.Tablet=Rg,a.TabletSmartphone=Fg,a.Tablets=Tg,a.Tag=Og,a.TagPlus=bg,a.TagX=qg,a.Tags=Ug,a.Tally1=Gg,a.Tally2=Zg,a.Tally3=Eg,a.Tally4=Ig,a.Tally5=Wg,a.Tangent=Xg,a.Target=Ng,a.Telescope=Kg,a.Tent=jg,a.TentTree=Jg,a.Terminal=Qg,a.TerminalSquare=ya,a.TestTube=Yg,a.TestTube2=ua,a.TestTubeDiagonal=ua,a.TestTubes=_g,a.Text=s,a.TextAlignCenter=ga,a.TextAlignEnd=Aa,a.TextAlignJustify=Ha,a.TextAlignJustifyCenter=xg,a.TextAlignJustifyEnd=aA,a.TextAlignJustifyStart=hA,a.TextAlignStart=s,a.TextCursor=dA,a.TextCursorInput=tA,a.TextInitial=Va,a.TextQuote=cA,a.TextSearch=MA,a.TextSelect=y,a.TextSelection=y,a.TextWrap=Sa,a.Theater=pA,a.Thermometer=nA,a.ThermometerSnowflake=lA,a.ThermometerSun=iA,a.ThumbsDown=eA,a.ThumbsUp=rA,a.TicTacToe=oA,a.Ticket=uA,a.TicketCheck=vA,a.TicketMinus=$A,a.TicketPercent=mA,a.TicketPlus=yA,a.TicketSlash=sA,a.TicketX=CA,a.Tickets=HA,a.TicketsPlane=gA,a.Timeline=AA,a.Timer=wA,a.TimerOff=VA,a.TimerReset=SA,a.ToggleLeft=LA,a.ToggleRight=fA,a.Toilet=kA,a.Tombstone=PA,a.ToolCase=BA,a.Toolbox=zA,a.Toothbrush=FA,a.ToothbrushSparkles=DA,a.Tornado=RA,a.Torus=bA,a.Touchpad=qA,a.TouchpadOff=TA,a.TowelRack=OA,a.TowerControl=UA,a.ToyBrick=GA,a.Tractor=ZA,a.TrafficCone=EA,a.Trailer=WA,a.Train=wa,a.TrainFront=XA,a.TrainFrontTunnel=IA,a.TrainTrack=NA,a.TramFront=wa,a.Transgender=KA,a.Trash=La,a.Trash2=La,a.TrashOff=JA,a.TreeDeciduous=QA,a.TreePalm=fa,a.TreePine=jA,a.Trees=YA,a.TrendingDown=_A,a.TrendingUp=aH,a.TrendingUpDown=xA,a.Triangle=dH,a.TriangleAlert=ka,a.TriangleDashed=hH,a.TriangleRight=tH,a.TrianglesCenterlineDashedHorizontal=Pa,a.TrianglesCenterlineDashedVertical=Ba,a.Trophy=cH,a.Truck=pH,a.TruckElectric=MH,a.TubeLotion=lH,a.TurkishLira=iH,a.Turntable=nH,a.Turtle=eH,a.Tv=oH,a.Tv2=za,a.TvMinimal=za,a.TvMinimalPlay=rH,a.Type=sH,a.TypeOutline=vH,a.Umbrella=mH,a.UmbrellaOff=$H,a.Underline=yH,a.Undo=gH,a.Undo2=CH,a.UndoDot=uH,a.UnfoldHorizontal=AH,a.UnfoldVertical=HH,a.Ungroup=VH,a.University=Da,a.Unlink=wH,a.Unlink2=SH,a.Unlock=U2,a.UnlockKeyhole=q2,a.Unplug=LH,a.Upload=fH,a.UploadCloud=P1,a.Usb=zH,a.UsbCPort=kH,a.User=JH,a.User2=Ua,a.UserCheck=PH,a.UserCheck2=Fa,a.UserCircle=S1,a.UserCircle2=V1,a.UserCog=BH,a.UserCog2=Ra,a.UserGroup=DH,a.UserKey=FH,a.UserLock=RH,a.UserMinus=bH,a.UserMinus2=ba,a.UserPen=qH,a.UserPlus=TH,a.UserPlus2=Ta,a.UserRound=Ua,a.UserRoundArrowLeft=UH,a.UserRoundCheck=Fa,a.UserRoundCog=Ra,a.UserRoundGroup=OH,a.UserRoundKey=GH,a.UserRoundMinus=ba,a.UserRoundPen=ZH,a.UserRoundPlus=Ta,a.UserRoundSearch=EH,a.UserRoundStar=WH,a.UserRoundX=qa,a.UserSearch=IH,a.UserShield=XH,a.UserSquare=sa,a.UserSquare2=ma,a.UserStar=NH,a.UserX=KH,a.UserX2=qa,a.Users=YH,a.Users2=Oa,a.UsersCollaboration=QH,a.UsersRound=Oa,a.UsersRoundCollaboration=jH,a.Utensils=Za,a.UtensilsCrossed=Ga,a.UtilityPole=_H,a.Van=xH,a.Variable=aV,a.Vault=hV,a.VectorPolygon=tV,a.VectorSquare=dV,a.Vegan=cV,a.VenetianMask=MV,a.Venus=lV,a.VenusAndMars=pV,a.Verified=k,a.Vibrate=nV,a.VibrateOff=iV,a.Video=rV,a.VideoOff=eV,a.Videotape=oV,a.View=vV,a.Virus=mV,a.VirusOff=$V,a.Voicemail=sV,a.Volleyball=yV,a.Volume=HV,a.Volume1=CV,a.Volume2=uV,a.VolumeOff=gV,a.VolumeX=AV,a.Vote=VV,a.Wallet=wV,a.Wallet2=Ea,a.WalletCards=SV,a.WalletMinimal=Ea,a.Wallpaper=LV,a.Wand=fV,a.Wand2=Wa,a.WandSparkles=Wa,a.Warehouse=kV,a.WashingMachine=PV,a.Watch=zV,a.Waves=Ia,a.WavesArrowDown=BV,a.WavesArrowUp=DV,a.WavesHorizontal=Ia,a.WavesLadder=FV,a.WavesVertical=RV,a.Waypoints=bV,a.Webcam=qV,a.WebcamOff=TV,a.Webhook=EV,a.WebhookOff=UV,a.Weight=GV,a.WeightTilde=OV,a.Wheat=WV,a.WheatOff=ZV,a.Whistle=IV,a.WholeWord=XV,a.Wifi=aS,a.WifiCog=NV,a.WifiHigh=KV,a.WifiLock=JV,a.WifiLow=QV,a.WifiOff=jV,a.WifiPen=YV,a.WifiSync=_V,a.WifiZero=xV,a.Wind=tS,a.WindArrowDown=dS,a.WindArrowUp=hS,a.Wine=MS,a.WineOff=cS,a.Workflow=pS,a.Worm=lS,a.WrapText=Sa,a.Wrench=nS,a.WrenchOff=iS,a.X=rS,a.XCircle=w1,a.XLineTop=eS,a.XOctagon=K2,a.XSquare=Ca,a.Zap=oS,a.ZapOff=vS,a.ZodiacAquarius=$S,a.ZodiacAries=mS,a.ZodiacCancer=yS,a.ZodiacCapricorn=sS,a.ZodiacGemini=CS,a.ZodiacLeo=uS,a.ZodiacLibra=AS,a.ZodiacOphiuchus=gS,a.ZodiacPisces=HS,a.ZodiacSagittarius=VS,a.ZodiacScorpio=SS,a.ZodiacTaurus=wS,a.ZodiacVirgo=LS,a.ZoomIn=fS,a.ZoomOut=kS,a.createElement=Ja,a.createIcons=BS,a.icons=PS})); +//# sourceMappingURL=lucide.min.js.map diff --git a/harborrl/webui/static/style.css b/harborrl/webui/static/style.css new file mode 100644 index 000000000..122b593ca --- /dev/null +++ b/harborrl/webui/static/style.css @@ -0,0 +1,1094 @@ +:root { + font-family: Inter, "Segoe UI", Arial, sans-serif; + color: #262a29; + background: #fff; + font-synthesis: none; + font-size: 14px; + letter-spacing: 0; + --green: #13745b; + --green-light: #edf7f2; + --border: #e3e7e4; + --muted: #727a75; + --red: #b83c3c; +} +* { + box-sizing: border-box; +} +body { + margin: 0; +} +button, +input, +select, +textarea { + font: inherit; + letter-spacing: 0; +} +button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + border: 1px solid transparent; + border-radius: 6px; + padding: 9px 14px; + min-height: 38px; + cursor: pointer; + background: none; + color: inherit; + font-weight: 550; + transition: + background 0.12s, + border-color 0.12s; +} +button:hover { + background: #f1f4f2; +} +button:disabled { + opacity: 0.45; + cursor: not-allowed; +} +button:focus-visible, +a:focus-visible { + outline: 2px solid #13745b; + outline-offset: 3px; +} +svg { + width: 17px; + height: 17px; + flex-shrink: 0; + stroke-width: 1.7; +} +input, +select, +textarea { + border: 1px solid #cdd5cf; + border-radius: 5px; + min-width: 0; + width: 100%; + background: #fff; + color: #282e2a; + padding: 10px 11px; + min-height: 40px; +} +input:focus, +select:focus, +textarea:focus { + outline: 2px solid #d1e9dd; + border-color: var(--green); +} +input:disabled { + background: #f5f7f5; + color: #666; +} +input[type="checkbox"] { + width: 16px; + height: 16px; + min-height: 16px; + accent-color: var(--green); +} +textarea { + resize: vertical; +} +label { + display: block; + font-size: 12px; + font-weight: 600; + margin-bottom: 7px; + color: #535d55; +} +h1, +h2, +h3, +p { + margin: 0; +} +h1 { + font-size: 25px; + font-weight: 620; + line-height: 1.25; +} +h2 { + font-size: 16px; + font-weight: 620; +} +h3 { + font-size: 13px; + font-weight: 650; +} +p { + line-height: 1.6; +} +a { + color: var(--green); +} +pre { + white-space: pre-wrap; + overflow-wrap: anywhere; +} +button.primary { + background: var(--green); + color: #fff; +} +button.primary:hover { + background: #0f614c; +} +button.secondary { + background: #fff; + border-color: #d9dfda; +} +button.secondary:hover { + background: #f6f8f6; + border-color: #b7c4bb; +} +button.danger { + color: var(--red); + background: #fff5f3; + border-color: #efd4cf; +} +.icon-button { + width: 36px; + height: 36px; + min-height: 36px; + padding: 8px; + flex-shrink: 0; +} +.muted { + color: var(--muted); + font-size: 12px; + line-height: 1.6; +} +.topbar { + height: 70px; + display: flex; + align-items: center; + border-bottom: 1px solid var(--border); + padding: 0 30px; + gap: 24px; + background: white; +} +.brand { + width: 173px; + display: flex; + flex-shrink: 0; +} +.brand img { + width: 100%; + height: auto; +} +.app-title { + font-size: 13px; + border-left: 1px solid var(--border); + padding-left: 24px; + color: #667168; +} +.connection { + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; + font-size: 12px; + color: #606c63; +} +.dot, +.status-dot { + width: 7px; + height: 7px; + background: #299474; + border-radius: 50%; + flex-shrink: 0; +} +.connection.offline .dot { + background: #c4863b; +} +.shell { + display: grid; + grid-template-columns: 228px minmax(0, 1fr); + min-height: calc(100vh - 70px); +} +.sidebar { + background: #f8faf8; + border-right: 1px solid var(--border); + padding: 28px 14px 20px; + display: flex; + flex-direction: column; + min-width: 0; +} +.nav-caption { + font-size: 10px; + font-weight: 650; + color: #879088; + letter-spacing: 1.1px; + padding: 0 14px; +} +.sidebar nav { + margin-top: 12px; + display: grid; + gap: 5px; +} +.nav-item { + justify-content: flex-start; + width: 100%; + padding: 10px 13px; + color: #677068; + font-size: 13px; +} +.nav-item.active { + background: #eaf2ec; + color: #176d51; +} +.nav-item svg { + margin-right: 2px; +} +.count { + margin-left: auto; + font-size: 10px; + min-width: 20px; + border-radius: 4px; + background: #e4eae5; + padding: 2px 5px; + text-align: center; +} +.saved-heading { + margin-top: 37px; + display: flex; + align-items: center; + justify-content: space-between; +} +.saved-heading .nav-caption { + font-size: 9px; + padding-right: 0; +} +.saved-list { + display: grid; + gap: 3px; + margin-top: 10px; +} +.saved-list > .muted { + padding: 0 14px; +} +.saved-item { + display: flex; + justify-content: flex-start; + min-width: 0; + font-size: 12px; + font-weight: 450; + padding: 9px 13px; + text-align: left; +} +.saved-item span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.saved-item.selected { + color: var(--green); + background: #eef4ef; +} +.sidebar-bottom { + margin-top: auto; + padding: 40px 13px 0; + color: #89928b; + display: flex; + align-items: flex-start; + gap: 8px; + font-size: 10px; +} +.sidebar-bottom span { + overflow-wrap: anywhere; + line-height: 1.6; +} +.sidebar-bottom svg { + width: 14px; + height: 14px; +} +main { + padding: 35px 38px 40px; + min-width: 0; + max-width: 1590px; + width: 100%; + margin: 0 auto; +} +.page-heading { + display: flex; + justify-content: space-between; + align-items: center; + gap: 18px; + margin-bottom: 27px; +} +.eyebrow { + color: #89938b; + font-size: 10px; + font-weight: 650; + letter-spacing: 1.2px; + margin-bottom: 9px; +} +.toolbar { + display: flex; + gap: 8px; + align-items: center; +} +.configuration-name { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 25px; +} +.configuration-name label { + margin: 0; + white-space: nowrap; +} +.configuration-name input { + max-width: 255px; + font-size: 13px; + height: 36px; + min-height: 36px; +} +.badge { + padding: 4px 7px; + background: #f0f2ef; + color: #667166; + font-size: 10px; + border-radius: 4px; + white-space: nowrap; +} +.badge.saved, +.badge.succeeded { + color: #176d51; + background: #e6f3eb; +} +.badge.failed { + color: #b43d3d; + background: #fbeceb; +} +.badge.running { + color: #176d51; + background: #e5f4eb; +} +.badge.stopping, +.badge.unknown { + color: #986719; + background: #fbf2dd; +} +.steps { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + border-bottom: 1px solid var(--border); + gap: 6px; + margin-bottom: 31px; +} +.steps button { + font-size: 12px; + border-radius: 0; + border-bottom: 2px solid transparent; + padding: 10px 2px 17px; + justify-content: flex-start; + min-width: 0; + color: #868c86; + gap: 9px; +} +.steps button > span { + height: 22px; + width: 22px; + border-radius: 50%; + border: 1px solid #d8ded9; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-size: 10px; +} +.steps button.active { + border-bottom-color: var(--green); + color: var(--green); +} +.steps button.active > span { + background: var(--green); + border-color: var(--green); + color: white; +} +.editor-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 242px; + gap: 38px; +} +.editor { + min-width: 0; +} +.section-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + margin-bottom: 20px; +} +.form-section { + margin-bottom: 27px; +} +.form-section + .form-section { + border-top: 1px solid var(--border); + padding-top: 25px; +} +.form-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px 18px; +} +.field.full { + grid-column: 1/-1; +} +.field small { + color: #8a938c; + font-size: 10px; + display: block; + margin-top: 5px; + line-height: 1.5; +} +.field.required label:after { + content: " *"; + color: #b9915a; +} +.checkbox-field { + display: flex; + align-items: center; + gap: 9px; + padding-top: 12px; +} +.checkbox-field label { + margin: 0; +} +.preset-options { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 9px; + margin-bottom: 26px; +} +.preset { + border: 1px solid var(--border); + border-radius: 6px; + min-height: 104px; + flex-direction: column; + align-items: flex-start; + justify-content: center; + text-align: left; + padding: 14px; + gap: 12px; + font-size: 12px; +} +.preset svg { + color: #839087; + width: 21px; + height: 21px; +} +.preset.active { + border-color: #72a88c; + background: #f2f8f3; + color: #116a4e; +} +.preset.active svg { + color: #13745b; +} +.preset small { + font-size: 10px; + font-weight: 400; + color: #859085; + display: block; + margin-top: 4px; +} +.summary { + border-left: 1px solid var(--border); + padding-left: 25px; + align-self: start; + position: sticky; + top: 20px; + min-width: 0; +} +.summary-heading { + display: flex; + gap: 8px; + align-items: center; + color: #607264; + margin-bottom: 20px; +} +.summary-heading h2 { + font-size: 12px; + color: #546659; +} +.topology-node { + display: grid; + grid-template-columns: 30px 1fr; + gap: 10px; + align-items: center; + padding: 10px 0; +} +.topology-node > .node-icon { + width: 30px; + height: 30px; + background: #f0f4ef; + display: flex; + align-items: center; + justify-content: center; + color: #617769; + border-radius: 5px; +} +.topology-node span { + display: block; + font-size: 10px; + color: #8a958c; + margin-top: 3px; + overflow-wrap: anywhere; +} +.topology-node strong { + font-size: 11px; + font-weight: 550; +} +.topology-connector { + height: 20px; + margin-left: 14px; + border-left: 1px dashed #c7d6ca; +} +.summary dl { + border-top: 1px solid var(--border); + margin: 21px 0 0; + padding: 19px 0 0; + display: grid; + grid-template-columns: 1fr auto; + gap: 13px; + font-size: 11px; +} +.summary dt { + color: #869087; +} +.summary dd { + margin: 0; + text-align: right; + font-weight: 550; + overflow-wrap: anywhere; + max-width: 110px; +} +.summary-footer { + border-top: 1px solid var(--border); + display: flex; + align-items: center; + gap: 7px; + margin-top: 21px; + padding-top: 17px; + font-size: 10px; + color: #929a93; +} +.summary-footer .status-dot { + background: #b2bbb0; + width: 5px; + height: 5px; +} +.editor-footer { + display: flex; + gap: 12px; + align-items: center; + padding-top: 23px; + border-top: 1px solid var(--border); + margin-top: 24px; +} +.editor-footer .muted { + margin-left: auto; + font-size: 10px; +} +.notice { + padding: 12px 15px; + margin-bottom: 22px; + background: #fbf5e8; + border: 1px solid #eadbbc; + border-radius: 5px; + color: #875e24; + font-size: 13px; + overflow-wrap: anywhere; +} +.notice.success { + background: #edf7ef; + border-color: #cfe5d4; + color: #296b42; +} +.validation-result { + margin-bottom: 24px; +} +.validation-row { + display: flex; + gap: 10px; + padding: 10px 0; + border-bottom: 1px solid #edf0ed; + font-size: 12px; + align-items: flex-start; +} +.validation-row svg { + width: 15px; + height: 15px; + margin-top: 2px; +} +.validation-row.passed svg { + color: var(--green); +} +.validation-row.failed svg { + color: var(--red); +} +.validation-row.deferred svg { + color: #b28943; +} +.validation-row strong { + display: block; + font-size: 11px; + font-weight: 600; + overflow-wrap: anywhere; + margin-bottom: 3px; +} +.validation-row p { + font-size: 11px; + color: #7c867e; +} +.check-status { + font-size: 12px; + font-weight: 600; + margin-bottom: 12px; + color: #5f6c63; +} +.file-tabs { + display: flex; + gap: 3px; + border: 1px solid var(--border); + border-bottom: 0; + background: #fafbf9; + padding: 5px 7px; + border-radius: 5px 5px 0 0; + overflow: auto; +} +.file-tabs button { + font-size: 10px; + min-height: 29px; + padding: 5px 9px; + white-space: nowrap; +} +.file-tabs button.active { + background: #eaf0e8; + color: #3e684b; +} +.code-panel { + font: + 11px/1.8 Consolas, + "SFMono-Regular", + monospace; + padding: 18px; + background: #fafbf9; + border: 1px solid var(--border); + margin: 0; + min-height: 130px; + max-height: 380px; + overflow: auto; + border-radius: 0 0 5px 5px; + color: #586758; +} +.preview-heading { + margin-bottom: 12px; +} +.launch-section { + margin-top: 26px; +} +.launch-section p { + font-size: 11px; + margin: 8px 0 15px; +} +.launch-actions { + display: flex; + gap: 9px; + flex-wrap: wrap; +} +.jobs-table { + width: 100%; + border-collapse: collapse; + font-size: 12px; + margin-top: 20px; +} +.jobs-table th { + text-align: left; + color: #8a938b; + font-size: 10px; + font-weight: 500; + padding: 14px 12px; + background: #f6f8f5; + border-bottom: 1px solid var(--border); +} +.jobs-table td { + padding: 16px 12px; + border-bottom: 1px solid var(--border); +} +.jobs-table tr[data-job] { + cursor: pointer; +} +.jobs-table tr[data-job]:hover, +.jobs-table tr.selected { + background: #f5f9f4; +} +.jobs-table .job-name { + font-weight: 550; +} +.jobs-table .job-id { + font-size: 10px; + color: #90998f; + margin-top: 3px; +} +.table-wrap { + overflow: auto; +} +.empty-state { + padding: 70px 20px; + text-align: center; + border-block: 1px solid var(--border); + color: #94a18f; +} +.empty-state svg { + width: 30px; + height: 30px; + margin-bottom: 16px; +} +.empty-state h2 { + color: #60735e; + margin-bottom: 8px; +} +.empty-state p { + font-size: 12px; +} +#job-detail { + margin-top: 35px; +} +#job-meta { + font-size: 11px; + overflow-wrap: anywhere; + margin-top: 8px; +} +.log-panel { + background: #1e2824; + color: #c3d3c8; + border-color: #1e2824; + border-radius: 5px; + min-height: 260px; + max-height: 510px; + font-size: 11px; +} +#log-note { + font-size: 10px; + margin-top: 10px; +} +dialog { + border: 1px solid #dce2da; + border-radius: 8px; + padding: 26px; + width: min(540px, calc(100% - 32px)); + box-shadow: 0 20px 90px #15291d25; + color: #354237; +} +dialog::backdrop { + background: #172a1c55; +} +dialog p { + font-size: 13px; + white-space: pre-wrap; + overflow-wrap: anywhere; + margin-top: 18px; +} +.dialog-heading { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} +.dialog-actions { + display: flex; + gap: 10px; + justify-content: flex-end; + margin-top: 25px; +} +#answers-dialog { + width: min(850px, calc(100% - 32px)); +} +#answers-editor { + font: + 12px/1.6 Consolas, + monospace; + height: 50vh; + margin-top: 18px; +} +.worker-heading { + display: flex; + justify-content: space-between; + align-items: center; + margin: 20px 0 12px; +} +.worker-group { + border-bottom: 1px solid var(--border); + padding-bottom: 20px; +} +.error-field { + border-color: #bf7770; +} +.small-select { + max-width: 130px; +} +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); +} +[hidden] { + display: none !important; +} +.mobile-only { + display: none; +} +.nav-caption, +.eyebrow { + letter-spacing: 0; +} +.log-selector { + display: flex; + align-items: center; + gap: 12px; + margin-top: 22px; +} +.log-selector label { + margin: 0; +} +@media (min-width: 1600px) { + main { + padding-left: 55px; + padding-right: 55px; + } + .editor-layout { + grid-template-columns: minmax(0, 1fr) 280px; + gap: 50px; + } + .summary { + padding-left: 32px; + } +} +@media (max-width: 1150px) { + .shell { + grid-template-columns: 195px minmax(0, 1fr); + } + main { + padding: 28px 24px; + } + .editor-layout { + grid-template-columns: minmax(0, 1fr) 195px; + gap: 23px; + } + .summary { + padding-left: 19px; + } + .preset { + padding: 11px; + } + .steps button { + font-size: 10px; + gap: 6px; + } + .saved-heading .nav-caption { + font-size: 8px; + padding-left: 10px; + } +} +@media (max-width: 900px) { + .editor-layout { + grid-template-columns: minmax(0, 1fr); + } + .summary { + display: none; + } + .shell { + grid-template-columns: 185px minmax(0, 1fr); + } + .page-heading .secondary { + font-size: 11px; + padding: 7px 9px; + } + .configuration-name { + gap: 10px; + } + .configuration-name label { + font-size: 10px; + } +} +@media (max-width: 650px) { + .mobile-only { + display: inline-flex; + } + .topbar { + height: 61px; + padding: 0 17px; + gap: 14px; + } + .brand { + width: 139px; + } + .app-title { + display: none; + } + .connection { + font-size: 10px; + } + .shell { + display: block; + min-height: calc(100vh - 61px); + } + .sidebar { + padding: 10px 14px; + border-right: 0; + border-bottom: 1px solid var(--border); + display: block; + } + .sidebar > .nav-caption, + .saved-heading, + .sidebar-bottom { + display: none; + } + .sidebar nav { + margin: 0; + display: flex; + gap: 10px; + } + .nav-item { + width: auto; + padding: 6px 12px; + font-size: 12px; + min-height: 34px; + } + .saved-list { + display: flex; + overflow: auto; + margin-top: 8px; + gap: 5px; + } + .saved-item { + padding: 5px 9px; + min-height: 30px; + max-width: 200px; + flex-shrink: 0; + } + .saved-list > .muted { + font-size: 10px; + padding-left: 12px; + } + main { + padding: 25px 18px; + } + .page-heading { + align-items: flex-start; + margin-bottom: 24px; + gap: 10px; + } + h1 { + font-size: 21px; + } + .eyebrow { + font-size: 9px; + } + .page-heading .secondary { + font-size: 0; + gap: 0; + width: 36px; + padding: 8px; + } + .page-heading .toolbar { + gap: 2px; + } + .configuration-name { + flex-wrap: wrap; + gap: 8px; + } + .configuration-name label { + width: 100%; + font-size: 11px; + } + .configuration-name input { + max-width: calc(100% - 65px); + } + .steps { + gap: 3px; + margin-bottom: 25px; + } + .steps button { + flex-direction: column; + align-items: center; + justify-content: flex-start; + font-size: 10px; + gap: 8px; + text-align: center; + line-height: 1.4; + padding: 8px 0 12px; + } + .steps button > span { + width: 20px; + height: 20px; + } + .form-grid { + grid-template-columns: minmax(0, 1fr); + gap: 17px; + } + .preset-options { + gap: 6px; + } + .preset { + min-height: 110px; + font-size: 10px; + gap: 8px; + padding: 10px 7px; + } + .preset small { + font-size: 9px; + line-height: 1.5; + } + .preset svg { + height: 18px; + width: 18px; + } + .section-heading h2 { + font-size: 14px; + } + .section-heading .secondary { + font-size: 10px; + padding: 7px 9px; + } + .editor-footer { + gap: 8px; + } + .editor-footer button { + font-size: 11px; + padding: 8px 10px; + } + .editor-footer .muted { + font-size: 9px; + } + .launch-actions { + gap: 7px; + } + .launch-actions button { + font-size: 11px; + padding: 8px 10px; + } + .jobs-table th, + .jobs-table td { + padding: 12px 8px; + } + .jobs-table th:nth-child(3), + .jobs-table td:nth-child(3) { + display: none; + } + .jobs-table { + font-size: 11px; + } + .code-panel { + font-size: 10px; + } + .dialog-actions { + flex-wrap: wrap; + } +} diff --git a/practices/h_cluster/README.md b/practices/h_cluster/README.md new file mode 100644 index 000000000..36fcd7cd3 --- /dev/null +++ b/practices/h_cluster/README.md @@ -0,0 +1,100 @@ +# H-cluster deployment practice + +This optional package adapts HarborRL to Brainbox and restricted Sandbox egress. +It shares the core Controller, scheduling, Harbor Runner and trajectory audit; +it does not implement another training loop. Installing HarborRL alone does not +install or import this package. All example addresses and paths are placeholders. + +## Topology + +```text +GPU training adapter -> developer Controller / RolloutService + -> local Harbor Runner -> Sandbox platform +Sandbox Claude -> loopback mailbox <- developer relay pump + -> developer Gateway -> GPU SGLang +``` + +The developer initiates authenticated platform-proxy requests. Sandbox needs +no direct route to the GPU or developer private listener. The developer must +reach the platform proxy and every advertised inference endpoint; the GPU +must reach the controller. Relay responses, including SSE, are fully buffered. + +## Install and configure + +Use Python 3.12+ for the runner. From the repository root: + +```bash +python -m pip install -e . -e practices/h_cluster +``` + +Install the practice in the runner environment. The controller can use a separate +Python environment with the dependencies required for its tokenizer and service. +Start with `configs/deployment.example.yaml` and `configs/sandbox.example.yaml`. +Use service placement, a local developer runner and no SSH rollout workers. +Set the training config's `harbor.environment.runtime` to `sandbox`, its +`config` to your Sandbox config, and `harbor.runner_transport` to `local`. + +For each task image create a matching platform Environment. Expose command +port 44772 and the configured relay port. Its capacity must cover the binding +limit; use prewarm 0 for an idle test deployment. Pin images and offline Claude +binary hashes. The all-zero example hash must be replaced. Put AK:SK credentials +in the referenced mode-600 file; never commit credentials or private site files. +Tasks and verifiers needing external packages must have those dependencies +prepared offline. The model relay is not a general internet proxy. + +The core only interprets common lifecycle settings. This package validates +project, Environment IDs, mounts and relay options. Whole-Gi memory values are +expressed using Gi. Endpoint GET 502/503/504 responses are retried within the +creation deadline; the allocation POST is never retried. Platform Pending can +still time out and must not be mistaken for ready capacity. + +## Verify and run + +No GPU or platform credentials are needed for the package's local HTTP/mock tests: + +```bash +python -m pip install -e '.[dev]' -e practices/h_cluster +python -m pytest practices/h_cluster/tests +python scripts/sandbox_sdk_contract.py +``` + +After filling in private configuration, use the runner interpreter: + +```bash +python -m harborrl.rollout.harbor_job.sandbox_probe /srv/harborrl/site/sandbox.yaml +python -m harborrl.rollout.harbor_job.sandbox_probe /srv/harborrl/site/sandbox.yaml --network +harborrl train /srv/harborrl/site/train.yaml --deployment /srv/harborrl/site/deployment.yaml --dry-run +``` + +The first command is local. The second performs authenticated, read-only +Environment checks and does not create instances. The dry run parses the launch +plan and does not contact the GPU. Check actual routes before starting training. +Removing `--dry-run` launches the selected GPU job and can allocate Sandbox +instances. Use a new run directory for each test. + +Inspect instance lifecycle files and cleanup-required records after cancellation. +An empty instance list can disagree with Environment capacity details; verify +both before reusing capacity. Never delete templates or instances belonging to +other runs. The framework records unresolved cleanup rather than assuming it +succeeded. It does not automatically replace platform Environment templates. + +## Configuration migration + +Experimental flat Brainbox configs are intentionally not accepted as standard +OpenSandbox settings. Replace `provider: brainbox` with +`adapter: harborrl_h_cluster.environment:BrainboxEnvironment`; move project and +relay settings under `options`, and Environment ID/mount requirements under +each binding's `options`. The platform always uses command port 44772. +Keep lifecycle limits and offline installation in their common top-level fields. + +## Operational limits + +Set concurrency within project quotas and each Environment's actual capacity. +Measure agent execution separately from allocated or Running instances. +Pending allocations, offline verifier dependencies, turn limits and invalid +generated tools can reject a complete training group. Session content must +match the recorded model response to pass consumption auditing. + +Start with a serial complete-group run before increasing concurrency. Inspect +rejected groups and confirm cleanup of every allocated instance. Checkpoint +creation and policy-version changes alone do not establish learning improvement. diff --git a/practices/h_cluster/configs/deployment.example.yaml b/practices/h_cluster/configs/deployment.example.yaml new file mode 100644 index 000000000..e89c2b62f --- /dev/null +++ b/practices/h_cluster/configs/deployment.example.yaml @@ -0,0 +1,37 @@ +# Replace example hosts and paths; no site-specific addresses are required. +schema_version: 1 +manager: + id: manager + mode: service + bind: 0.0.0.0 + control_port: 49200 + control_url: http://manager.example:49200 + gateway_port: 49184 + gateway_url: http://manager.example:49184 + run_token_file: /srv/harborrl/site/run-token + source_dir: /srv/harborrl/repo + controller_python: /opt/harborrl/controller/bin/python +gpu_target: + id: trainer + transport: ssh + ssh: {host: training-host} + source_dir: /srv/harborrl/repo + train_python: /opt/harborrl/training/bin/python + environment_file: /srv/harborrl/site/gpu-env.json + expected_gpus: 8 +storage: + mode: shared_fs + run_root: /srv/harborrl/runs + manager_writable: true + gpu_writable: true + worker_writable: true +rollout: + provider: sandbox + workers: [] +connectivity: + command_timeout_s: 30 + ssh_timeout_s: 15 + http_timeout_s: 10 + gpu_to_controller: true + worker_to_gateway: false + manager_to_gpu_inference: true diff --git a/practices/h_cluster/configs/sandbox.example.yaml b/practices/h_cluster/configs/sandbox.example.yaml new file mode 100644 index 000000000..6229e8659 --- /dev/null +++ b/practices/h_cluster/configs/sandbox.example.yaml @@ -0,0 +1,23 @@ +adapter: harborrl_h_cluster.environment:BrainboxEnvironment +domain: https://sandbox.example/brainbox +credential_file: /run/secrets/harborrl/sandbox-key +max_live_instances: 8 +startup_concurrency: 1 +request_timeout_s: 180 +create_timeout_s: 600 +options: + project: REPLACE_PROJECT + gateway_transport: platform_pull + relay_port: 49185 + relay_timeout_s: 180 +bindings: + - image: registry.example/project/task:locked-version + max_live_instances: 1 + options: + environment_id: REPLACE_ENVIRONMENT_ID + required_mount_paths: [] +installation: + mode: offline_binary + version: 2.1.141 + binary: /opt/harborrl/assets/claude + sha256: '0000000000000000000000000000000000000000000000000000000000000000' diff --git a/practices/h_cluster/pyproject.toml b/practices/h_cluster/pyproject.toml new file mode 100644 index 000000000..0e06e0611 --- /dev/null +++ b/practices/h_cluster/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["setuptools>=69", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "harborrl-h-cluster" +version = "0.1.0" +description = "Optional H-cluster deployment practice for HarborRL" +requires-python = ">=3.12" +dependencies = ["harborrl>=0.1.0", "harbor==0.23.0", "opensandbox==0.1.9", "httpx>=0.27,<1"] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/practices/h_cluster/src/harborrl_h_cluster/__init__.py b/practices/h_cluster/src/harborrl_h_cluster/__init__.py new file mode 100644 index 000000000..e8e8a73b2 --- /dev/null +++ b/practices/h_cluster/src/harborrl_h_cluster/__init__.py @@ -0,0 +1 @@ +"""Optional H-cluster deployment adapters; not imported by HarborRL core.""" diff --git a/practices/h_cluster/src/harborrl_h_cluster/environment.py b/practices/h_cluster/src/harborrl_h_cluster/environment.py new file mode 100644 index 000000000..6b0b68527 --- /dev/null +++ b/practices/h_cluster/src/harborrl_h_cluster/environment.py @@ -0,0 +1,143 @@ +"""Brainbox-specific settings and relay on the shared audited lifecycle.""" +import asyncio +import re +from contextlib import suppress + +import httpx + +from harborrl.rollout.harbor_job.environments.opensandbox import ( + ManagedOpenSandboxEnvironment, +) +from harborrl.rollout.harbor_job.sandbox_config import read_credential +from harborrl.trajectories.native import publish +from harborrl_h_cluster.transport import SandboxStartupTransport + + +async def validate_environment(cfg, binding, credential): + options, target = cfg['options'], binding['options'] + async with httpx.AsyncClient(timeout=cfg['request_timeout_s']) as client: + response = await client.get( + cfg['domain'].rstrip('/') + '/v1/sandbox-environments/' + target['environment_id'], + params={'project': options['project']}, headers={'OPEN-SANDBOX-API-KEY': credential}) + if response.status_code != 200: + raise RuntimeError(f'Environment query failed: HTTP {response.status_code}') + environment = response.json() + if environment.get('image', {}).get('uri') != binding['image']: + raise ValueError('Environment image does not match task image') + required_ports = {44772} + if options['gateway_transport'] == 'platform_pull': + required_ports.add(options['relay_port']) + if not required_ports <= {p.get('containerPort') for p in environment.get('ports', [])}: + raise ValueError('Environment command or relay port missing') + capacity = environment.get('instanceCapacity') + if type(capacity) is not int or capacity < binding['max_live_instances']: + raise ValueError('Environment capacity below configured admission limit') + mounts = {v.get('mountPath') for v in environment.get('volumes', [])} + if set(target['required_mount_paths']) - mounts: + raise ValueError('Environment required mounts missing') + return {'environment_id': target['environment_id'], 'image': binding['image'], 'capacity': capacity} + + +class BrainboxEnvironment(ManagedOpenSandboxEnvironment): + @classmethod + def validate_config(cls, cfg): + options = cfg['options'] + if set(options) != {'project', 'gateway_transport', 'relay_port', 'relay_timeout_s'}: + raise ValueError('H-cluster options require project, gateway_transport, relay_port, relay_timeout_s') + if not isinstance(options['project'], str) or not options['project'].strip(): + raise ValueError('H-cluster project required') + if options['gateway_transport'] not in ('direct', 'platform_pull') or cfg['use_server_proxy'] is not True: + raise ValueError('H-cluster requires server proxy and a supported gateway transport') + if (type(options['relay_port']) is not int or not 0 < options['relay_port'] < 65536 + or options['relay_port'] == 44772 or type(options['relay_timeout_s']) is not int + or options['relay_timeout_s'] < 30): + raise ValueError('invalid H-cluster relay port/timeout') + seen = set() + for binding in cfg['bindings']: + target = binding['options'] + if set(target) != {'environment_id', 'required_mount_paths'}: + raise ValueError('H-cluster binding requires environment_id and required_mount_paths') + eid = target['environment_id'] + if not isinstance(eid, str) or not re.fullmatch(r'[A-Za-z0-9_-]+', eid) or eid in seen: + raise ValueError('invalid or duplicate H-cluster Environment ID') + seen.add(eid) + if not isinstance(target['required_mount_paths'], list) or any( + not isinstance(p, str) or not p.startswith('/') for p in target['required_mount_paths']): + raise ValueError('invalid required_mount_paths') + + @classmethod + def agent_gateway(cls, cfg, gateway_url): + if cfg['options']['gateway_transport'] == 'platform_pull': + return f"http://127.0.0.1:{cfg['options']['relay_port']}" + return gateway_url + + @classmethod + async def network_check(cls, cfg): + credential = read_credential(cfg) + if ':' not in credential: + raise ValueError('H-cluster credential must contain AK:SK') + return [await validate_environment(cfg, binding, credential) for binding in cfg['bindings']] + + def __init__(self, *args, **kwargs): + self.relay_task = None + super().__init__(*args, **kwargs) + self.record['environment_id'] = self.binding['options']['environment_id'] + + async def validate_binding(self): + if ':' not in self._api_key: + raise ValueError('H-cluster credential must contain AK:SK') + await validate_environment(self.cfg, self.binding, self._api_key) + + def extensions(self): + return {'environmentId': self.binding['options']['environment_id']} + + def api_params(self): + return {'project': self.cfg['options']['project']} + + def startup_transport(self, allocated): + return SandboxStartupTransport(self.cfg['domain'].rstrip('/') + '/v1', allocated, + timeout=self.cfg['create_timeout_s']) + + def _build_resource(self): + resource = super()._build_resource() + memory = re.fullmatch(r'(\d+)Mi', resource.get('memory', '')) + if memory and int(memory[1]) % 1024 == 0: + resource['memory'] = f'{int(memory[1]) // 1024}Gi' + return resource + + async def start(self, force_build): + try: + await super().start(force_build) + if self.cfg['options']['gateway_transport'] == 'platform_pull': + from harborrl_h_cluster.relay import start_relay + if not self.gateway_url: + raise ValueError('platform relay requires manager Gateway URL') + self.relay_task = await start_relay(self, self.gateway_url) + except BaseException: + if self._sandbox is not None: + await self.stop(delete=True) + raise + + async def exec(self, *args, **kwargs): + if self.relay_task is None: + return await super().exec(*args, **kwargs) + command = asyncio.create_task(super().exec(*args, **kwargs)) + try: + done, _ = await asyncio.wait((command, self.relay_task), return_when=asyncio.FIRST_COMPLETED) + if self.relay_task in done: + publish(self.evidence / 'relay-failed.json', {'reason': 'manager relay pump stopped'}) + raise RuntimeError('manager relay pump stopped; attempt invalid') + return await command + finally: + if not command.done(): + command.cancel() + with suppress(asyncio.CancelledError): + await command + + async def stop(self, delete=True): + if self.relay_task is not None: + task, self.relay_task = self.relay_task, None + task.cancel() + with suppress(asyncio.CancelledError, Exception): + await task + await super().stop(delete=delete) diff --git a/practices/h_cluster/src/harborrl_h_cluster/relay.py b/practices/h_cluster/src/harborrl_h_cluster/relay.py new file mode 100644 index 000000000..3bf7d5b32 --- /dev/null +++ b/practices/h_cluster/src/harborrl_h_cluster/relay.py @@ -0,0 +1,50 @@ +"""Lifecycle of one manager-initiated relay inside a real sandbox.""" +import asyncio +import secrets +import tempfile +from pathlib import Path + +import httpx + +from harborrl.trajectories.native import publish +from harborrl_h_cluster import relay_server +from harborrl_h_cluster.reverse_relay import pump + + +async def start_relay(environment, gateway_url): + cfg = {**environment.cfg, **environment.cfg['options']} + token = secrets.token_hex(32) + await environment.upload_file(Path(relay_server.__file__), '/tmp/harborrl-relay.py') + with tempfile.NamedTemporaryFile(mode='w', encoding='ascii') as secret: + secret.write(token) + secret.flush() + await environment.upload_file(Path(secret.name), '/tmp/harborrl-relay-token') + result = await environment.exec('chmod 600 /tmp/harborrl-relay-token && ' + 'nohup python3 /tmp/harborrl-relay.py --token-file /tmp/harborrl-relay-token ' + f"--port {cfg['relay_port']} --timeout {cfg['relay_timeout_s']} " + '> /tmp/harborrl-relay.log 2>&1 < /dev/null &') + if result.return_code: + raise RuntimeError('sandbox relay process failed to start') + endpoint = await environment._sandbox.get_endpoint(cfg['relay_port']) + url = endpoint.endpoint + if not url.startswith(('http://', 'https://')): + url = cfg['protocol'] + '://' + url + headers = endpoint.headers or {} + async with httpx.AsyncClient(timeout=10, trust_env=False) as client: + for _ in range(20): + try: + response = await client.get(url.rstrip('/') + '/relay/health', + headers={**headers, 'X-Relay-Token': token}) + if response.status_code == 200 and response.json() == {'ok': True}: + break + except (httpx.HTTPError, ValueError): + pass + await asyncio.sleep(1) + else: + raise RuntimeError('sandbox relay readiness failed') + publish(environment.evidence / 'relay-ready.json', { + 'transport': 'platform_pull', 'port': cfg['relay_port'], + 'upstream': gateway_url, 'sandbox_id': environment.record['sandbox_id'], + 'response_mode': 'buffered', 'timeout_s': cfg['relay_timeout_s']}) + return asyncio.create_task(pump(url, headers, token, gateway_url, asyncio.Event(), + timeout=cfg['relay_timeout_s'] - 10)) diff --git a/practices/h_cluster/src/harborrl_h_cluster/relay_server.py b/practices/h_cluster/src/harborrl_h_cluster/relay_server.py new file mode 100644 index 000000000..689d47c1c --- /dev/null +++ b/practices/h_cluster/src/harborrl_h_cluster/relay_server.py @@ -0,0 +1,150 @@ +"""Sandbox-local HTTP mailbox. Standalone stdlib file, uploaded by the runner. + +The manager pulls each request once. Upstream inference is never retried after +an ambiguous transport failure. This prototype buffers responses, including SSE. +""" +import argparse +import base64 +import hmac +import json +import queue +import secrets +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path + +MAX_BODY = 16 * 1024 * 1024 +PATHS = {'/v1/messages', '/v1/messages/count_tokens'} +REQUEST_HEADERS = {'content-type', 'x-api-key', 'authorization', 'anthropic-version', 'anthropic-beta'} +RESPONSE_HEADERS = {'content-type', 'request-id', 'retry-after'} + + +class Mailbox: + def __init__(self, token, *, timeout=120, capacity=8): + self.token, self.timeout = token, timeout + self.slots = threading.BoundedSemaphore(capacity) + self.queue = queue.Queue(maxsize=capacity) + self.pending = {} + self.lock = threading.Lock() + + +def make_server(host, port, mailbox): + class Handler(BaseHTTPRequestHandler): + def log_message(self, *args): + pass + + def reply(self, status, body, headers=None): + self.send_response(status) + for key, value in (headers or {}).items(): + if key.lower() in RESPONSE_HEADERS: + self.send_header(key, value) + self.send_header('Content-Length', str(len(body))) + self.end_headers() + self.wfile.write(body) + + def json_reply(self, status, data): + self.reply(status, json.dumps(data).encode(), {'content-type': 'application/json'}) + + def authorized(self): + return hmac.compare_digest(self.headers.get('X-Relay-Token', ''), mailbox.token) + + def read_body(self): + length = int(self.headers.get('Content-Length', '0')) + if not 0 <= length <= MAX_BODY or self.headers.get('Transfer-Encoding'): + raise ValueError('invalid body length') + return self.rfile.read(length) + + def do_GET(self): + if not self.authorized(): + self.json_reply(403, {'error': 'forbidden'}) + return + if self.path == '/relay/health': + self.json_reply(200, {'ok': True}) + return + if self.path != '/relay/next': + self.json_reply(404, {'error': 'not_found'}) + return + while True: + try: + item = mailbox.queue.get_nowait() + except queue.Empty: + self.reply(204, b'') + return + with mailbox.lock: + active = item['id'] in mailbox.pending + if active: + self.json_reply(200, item) + return + + def do_POST(self): + try: + self.handle_post() + except (ValueError, KeyError, TypeError): + self.json_reply(400, {'error': 'invalid_request'}) + + def handle_post(self): + if self.path.startswith('/relay/result/'): + if not self.authorized(): + self.json_reply(403, {'error': 'forbidden'}) + return + data = json.loads(self.read_body()) + if type(data['status']) is not int or not 200 <= data['status'] <= 599: + raise ValueError('invalid status') + body = base64.b64decode(data['body'], validate=True) + headers = data['headers'] + if not isinstance(headers, dict) or any( + not isinstance(k, str) or not isinstance(v, str) or '\r' in v or '\n' in v + for k, v in headers.items() + ): + raise ValueError('invalid response headers') + with mailbox.lock: + pending = mailbox.pending.get(self.path.rsplit('/', 1)[-1]) + if pending is None or pending['event'].is_set(): + self.json_reply(409, {'error': 'request_expired_or_completed'}) + return + pending['result'] = (data['status'], body, headers) + pending['event'].set() + self.json_reply(200, {'ok': True}) + return + if self.path.split('?', 1)[0] not in PATHS: + self.json_reply(404, {'error': 'not_found'}) + return + body = self.read_body() + if not mailbox.slots.acquire(blocking=False): + self.json_reply(503, {'error': 'relay_capacity'}) + return + identity = secrets.token_hex(16) + pending = {'event': threading.Event()} + try: + with mailbox.lock: + mailbox.pending[identity] = pending + try: + mailbox.queue.put_nowait({'id': identity, 'path': self.path, 'method': 'POST', + 'headers': {k: v for k, v in self.headers.items() if k.lower() in REQUEST_HEADERS}, + 'body': base64.b64encode(body).decode()}) + except queue.Full: + self.json_reply(503, {'error': 'relay_queue_full'}) + return + if not pending['event'].wait(mailbox.timeout): + self.json_reply(504, {'error': 'relay_deadline'}) + return + self.reply(*pending['result']) + finally: + with mailbox.lock: + mailbox.pending.pop(identity, None) + mailbox.slots.release() + + server = ThreadingHTTPServer((host, port), Handler) + server.daemon_threads = True + return server + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--token-file', required=True) + parser.add_argument('--port', type=int, default=49185) + parser.add_argument('--timeout', type=float, default=120) + args = parser.parse_args() + server = make_server('0.0.0.0', args.port, + Mailbox(Path(args.token_file).read_text().strip(), timeout=args.timeout)) + server.serve_forever() diff --git a/practices/h_cluster/src/harborrl_h_cluster/reverse_relay.py b/practices/h_cluster/src/harborrl_h_cluster/reverse_relay.py new file mode 100644 index 000000000..86316dbac --- /dev/null +++ b/practices/h_cluster/src/harborrl_h_cluster/reverse_relay.py @@ -0,0 +1,49 @@ +"""Manager-initiated sandbox relay over the authenticated platform proxy. + +Experimental transport: HTTP/SSE bodies are buffered and no upstream POST is +retried. Credentials and request bodies must never enter diagnostic logs. +""" +import asyncio +import base64 + +import httpx + +from .relay_server import MAX_BODY, PATHS, REQUEST_HEADERS, RESPONSE_HEADERS + + +async def pump(proxy_url, proxy_headers, relay_token, gateway_url, stop, *, timeout=110): + headers = {**proxy_headers, 'X-Relay-Token': relay_token} + async with httpx.AsyncClient(timeout=timeout, trust_env=False) as upstream, \ + httpx.AsyncClient(timeout=30, trust_env=False, headers=headers) as platform: + while not stop.is_set(): + pending = await platform.get(proxy_url.rstrip('/') + '/relay/next') + if pending.status_code == 204: + await asyncio.sleep(0.25) + continue + pending.raise_for_status() + item = pending.json() + if (item['method'] != 'POST' or item['path'].split('?', 1)[0] not in PATHS + or not isinstance(item['id'], str) + or len(item['id']) != 32 or any(c not in '0123456789abcdef' for c in item['id'])): + raise ValueError('invalid relay request') + body = base64.b64decode(item['body'], validate=True) + if len(body) > MAX_BODY: + raise ValueError('relay body too large') + request_headers = {k: v for k, v in item['headers'].items() if k.lower() in REQUEST_HEADERS} + try: + async with upstream.stream('POST', gateway_url.rstrip('/') + item['path'], + content=body, headers=request_headers) as response: + content = bytearray() + async for chunk in response.aiter_bytes(): + content.extend(chunk) + if len(content) > MAX_BODY // 2: + raise ValueError('relay response too large') + result = {'status': response.status_code, + 'headers': {k: v for k, v in response.headers.items() + if k.lower() in RESPONSE_HEADERS}, + 'body': base64.b64encode(content).decode()} + except (httpx.HTTPError, ValueError): + result = {'status': 502, 'headers': {'content-type': 'application/json'}, + 'body': base64.b64encode(b'{"error":"upstream_failed_no_retry"}').decode()} + delivered = await platform.post(proxy_url.rstrip('/') + '/relay/result/' + item['id'], json=result) + delivered.raise_for_status() diff --git a/practices/h_cluster/src/harborrl_h_cluster/transport.py b/practices/h_cluster/src/harborrl_h_cluster/transport.py new file mode 100644 index 000000000..b72151681 --- /dev/null +++ b/practices/h_cluster/src/harborrl_h_cluster/transport.py @@ -0,0 +1,28 @@ +"""Bounded retries for H-platform endpoint discovery, never allocation POSTs.""" +import asyncio +import re +import time + +from harborrl.rollout.harbor_job.sandbox_transport import ( + SandboxStartupTransport as AllocationTransport, +) + + +class SandboxStartupTransport(AllocationTransport): + def __init__(self, base_url, on_created, *, timeout, transport=None, retry_delay=2): + super().__init__(base_url, on_created, transport=transport) + self.deadline = time.monotonic() + timeout + self.retry_delay = retry_delay + + async def handle_async_request(self, request): + url = str(request.url).split('?', 1)[0] + endpoint_get = request.method == 'GET' and re.fullmatch( + re.escape(self.base_url) + r'/sandboxes/[A-Za-z0-9_-]+/endpoints/\d+', url) + while True: + response = await super().handle_async_request(request) + if not endpoint_get or response.status_code not in (502, 503, 504): + return response + if self.deadline - time.monotonic() <= self.retry_delay: + return response + await response.aclose() + await asyncio.sleep(self.retry_delay) diff --git a/practices/h_cluster/tests/test_environment.py b/practices/h_cluster/tests/test_environment.py new file mode 100644 index 000000000..95ea0dda3 --- /dev/null +++ b/practices/h_cluster/tests/test_environment.py @@ -0,0 +1,69 @@ +import asyncio + +import httpx +import pytest +from harbor.environments.opensandbox import OpenSandboxEnvironment +from harborrl_h_cluster.environment import BrainboxEnvironment, validate_environment + + +def settings(): + return {'domain': 'https://sandbox.example', 'request_timeout_s': 1, 'use_server_proxy': True, + 'options': {'project': 'test', 'gateway_transport': 'platform_pull', 'relay_port': 49185, + 'relay_timeout_s': 180}, + 'bindings': [{'image': 'image', 'max_live_instances': 2, + 'options': {'environment_id': 'env-test', 'required_mount_paths': ['/data']}}]} + + +@pytest.mark.parametrize('memory,normalized', [('1024Mi', '1Gi'), ('2048Mi', '2Gi'), ('512Mi', '512Mi')]) +def test_resource_units(monkeypatch, memory, normalized): + monkeypatch.setattr(OpenSandboxEnvironment, '_build_resource', lambda _: {'cpu': '2', 'memory': memory}) + assert object.__new__(BrainboxEnvironment)._build_resource() == {'cpu': '2', 'memory': normalized} + + +@pytest.mark.parametrize('change', ['port', 'project', 'binding', 'duplicate']) +def test_options_reject_invalid_platform_configuration(change): + cfg = settings() + if change == 'port': + cfg['options']['relay_port'] = 44772 + elif change == 'project': + cfg['options']['project'] = '' + elif change == 'binding': + cfg['bindings'][0]['options']['environment_id'] = '../other' + else: + cfg['bindings'].append(cfg['bindings'][0]) + with pytest.raises(ValueError): + BrainboxEnvironment.validate_config(cfg) + + +def test_platform_routing_and_extensions(): + cfg = settings() + BrainboxEnvironment.validate_config(cfg) + assert BrainboxEnvironment.agent_gateway(cfg, 'http://gateway') == 'http://127.0.0.1:49185' + env = object.__new__(BrainboxEnvironment) + env.cfg, env.binding = cfg, cfg['bindings'][0] + assert env.extensions() == {'environmentId': 'env-test'} + assert env.api_params() == {'project': 'test'} + cfg['options']['gateway_transport'] = 'direct' + assert BrainboxEnvironment.agent_gateway(cfg, 'http://gateway') == 'http://gateway' + + +@pytest.mark.parametrize('ports,capacity,mounts,accepted', [ + ([44772, 49185], 2, ['/data'], True), ([44772], 2, ['/data'], False), + ([44772, 49185], 1, ['/data'], False), ([44772, 49185], 2, [], False), +]) +def test_environment_preflight_checks_platform_contract(monkeypatch, ports, capacity, mounts, accepted): + cfg = settings() + + def handler(request): + assert request.method == 'GET' + assert request.url.params['project'] == 'test' + return httpx.Response(200, json={'image': {'uri': 'image'}, 'instanceCapacity': capacity, + 'ports': [{'containerPort': p} for p in ports], 'volumes': [{'mountPath': p} for p in mounts]}) + + client = httpx.AsyncClient + monkeypatch.setattr(httpx, 'AsyncClient', lambda **kw: client(**kw, transport=httpx.MockTransport(handler))) + if accepted: + assert asyncio.run(validate_environment(cfg, cfg['bindings'][0], 'test-only'))['capacity'] == 2 + else: + with pytest.raises(ValueError): + asyncio.run(validate_environment(cfg, cfg['bindings'][0], 'test-only')) diff --git a/practices/h_cluster/tests/test_relay.py b/practices/h_cluster/tests/test_relay.py new file mode 100644 index 000000000..b8e98007b --- /dev/null +++ b/practices/h_cluster/tests/test_relay.py @@ -0,0 +1,137 @@ +import asyncio +import base64 +import contextlib +import json +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +import httpx +import pytest +from harborrl_h_cluster.relay_server import Mailbox, make_server +from harborrl_h_cluster.reverse_relay import pump + + +@contextlib.contextmanager +def serving(server): + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + yield f'http://127.0.0.1:{server.server_port}' + finally: + server.shutdown() + server.server_close() + thread.join(5) + + +def test_relay_auth_timeout_and_no_redelivery(): + async def run(url): + async with httpx.AsyncClient(trust_env=False) as client: + assert (await client.get(url + '/relay/next')).status_code == 403 + assert (await client.post(url + '/unexpected', content=b'x')).status_code == 404 + pending = asyncio.create_task(client.post(url + '/v1/messages', content=b'body')) + headers = {'X-Relay-Token': 'secret'} + for _ in range(100): + item = await client.get(url + '/relay/next', headers=headers) + if item.status_code == 200: + break + await asyncio.sleep(.01) + assert item.status_code == 200 + assert (await client.get(url + '/relay/next', headers=headers)).status_code == 204 + assert (await pending).status_code == 504 + delivered = await client.post(url + '/relay/result/' + item.json()['id'], + headers=headers, json={'status': 200, 'headers': {}, 'body': base64.b64encode(b'late').decode()}) + assert delivered.status_code == 409 + with serving(make_server('127.0.0.1', 0, Mailbox('secret', timeout=.5))) as url: + asyncio.run(run(url)) + + +@pytest.mark.parametrize('status,content_type,body', [ + (200, 'text/event-stream', b'event: message_start\ndata: {"id":"test"}\n\n'), + (429, 'application/json', b'{"error":"rate_limited"}'), +]) +def test_reverse_roundtrip_preserves_bytes_and_headers(status, content_type, body): + calls = [] + class Upstream(BaseHTTPRequestHandler): + def log_message(self, *args): + pass + def do_POST(self): + calls.append((self.path, self.headers.get('x-api-key'), + self.rfile.read(int(self.headers['Content-Length'])))) + self.send_response(status) + self.send_header('Content-Type', content_type) + self.send_header('Content-Length', str(len(body))) + self.end_headers() + self.wfile.write(body) + async def run(relay, gateway): + stop = asyncio.Event() + task = asyncio.create_task(pump(relay, {}, 'secret', gateway, stop)) + try: + async with httpx.AsyncClient(trust_env=False) as client: + response = await client.post(relay + '/v1/messages?beta=true', + headers={'x-api-key': 'attempt-token'}, content=b'{"stream":true}') + assert response.status_code == status + assert response.content == body + assert response.headers['content-type'] == content_type + finally: + stop.set() + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task + with serving(make_server('127.0.0.1', 0, Mailbox('secret'))) as relay, \ + serving(ThreadingHTTPServer(('127.0.0.1', 0), Upstream)) as gateway: + asyncio.run(run(relay, gateway)) + assert calls == [('/v1/messages?beta=true', 'attempt-token', b'{"stream":true}')] + + +def test_real_gateway_trace_and_session_audit_through_relay(tmp_path): + from harborrl.gateway.server import Gateway + from harborrl.gateway.server import make_server as make_gateway + from harborrl.gateway.trace import TraceRegistry + from harborrl.rollout.harbor_job.audit import audit_session + from harborrl.trajectories.native import Identity, RewardProfile, digest + + class Backend: + def count_tokens(self, converted): + return 2 + + def generate(self, converted, ident, response_id): + return {'input_ids': [1, 2], 'output_ids': [3], 'logprobs': [-.2], 'logprob_semantics': 'raw_model', + 'policy_version': ident.policy_version, 'tokenizer_digest': 'tok', 'template_digest': 'tpl', + 'engine_id': 'synthetic', 'serving_input': {'input_ids': [1, 2]}, 'sampling': converted['sampling'], + 'finish_reason': 'end_turn', 'evidence_kind': 'synthetic', 'content': [{'type': 'text', 'text': 'done'}]} + + sampling = {'temperature': 1.0, 'top_p': 1.0, 'top_k': -1, 'max_new_tokens': 64, 'stop': []} + identity = Identity(run_id='run', batch_id='batch', group_id='group', slot_id='0', attempt_id='attempt', + trajectory_id='trajectory', task_digest='task', policy_version='1', harness_digest='claude', + reward_digest=RewardProfile().digest, sampling_digest=digest(sampling)) + registry = TraceRegistry(tmp_path / 'traces') + credential = registry.register(identity, 'trial') + gateway = Gateway(registry, Backend(), model='policy') + + async def run(relay, upstream): + stop = asyncio.Event() + task = asyncio.create_task(pump(relay, {}, 'secret', upstream, stop)) + try: + async with httpx.AsyncClient(trust_env=False) as client: + response = await client.post(relay + '/v1/messages', + headers={'x-api-key': credential}, json={'model': 'policy', 'max_tokens': 64, + 'messages': [{'role': 'user', 'content': 'hello'}]}) + assert response.status_code == 200 + message = response.json() + session = tmp_path / 'session.jsonl' + session.write_text(json.dumps({'sessionId': 'relay-session', 'type': 'assistant', + 'message': message}) + '\n') + audit_session(session, registry, identity.attempt_id) + registry.close(identity.attempt_id) + turns, seal = registry.seal(identity.attempt_id) + assert seal['errors'] == [] + assert len(turns) == 1 + assert turns[0]['delivery'] == 'consumed_confirmed' + finally: + stop.set() + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task + with serving(make_server('127.0.0.1', 0, Mailbox('secret'))) as relay, \ + serving(make_gateway(gateway)) as upstream: + asyncio.run(run(relay, upstream)) diff --git a/practices/h_cluster/tests/test_transport.py b/practices/h_cluster/tests/test_transport.py new file mode 100644 index 000000000..9e56c93df --- /dev/null +++ b/practices/h_cluster/tests/test_transport.py @@ -0,0 +1,66 @@ +import asyncio + +import httpx +from harborrl_h_cluster.transport import SandboxStartupTransport + + +def test_transient_endpoint_retries_do_not_recreate_sandbox(): + async def run(): + calls, ids = [], [] + + async def serve(request): + calls.append((request.method, request.url.path)) + if request.method == 'POST': + return httpx.Response(201, json={'id': 'sbx-one'}) + if len(calls) < 4: + return httpx.Response(503) + return httpx.Response(200, json={'endpoint': 'ready'}) + + transport = SandboxStartupTransport('https://platform.test/v1', ids.append, timeout=10, + transport=httpx.MockTransport(serve), retry_delay=0) + async with httpx.AsyncClient(transport=transport) as client: + await client.post('https://platform.test/v1/sandboxes', json={}) + response = await client.get('https://platform.test/v1/sandboxes/sbx-one/endpoints/44772') + assert response.status_code == 200 + assert ids == ['sbx-one'] + assert sum(method == 'POST' for method, _ in calls) == 1 + assert len(calls) == 4 + + asyncio.run(run()) + + +def test_mutations_proxy_requests_and_auth_failures_are_not_retried(): + async def run(): + calls = [] + + async def serve(request): + calls.append(request) + return httpx.Response(401 if '/endpoints/' in request.url.path else 503) + + transport = SandboxStartupTransport('https://platform.test/v1', lambda _: None, timeout=10, + transport=httpx.MockTransport(serve), retry_delay=0) + async with httpx.AsyncClient(transport=transport) as client: + await client.post('https://platform.test/v1/sandboxes') + await client.get('https://platform.test/v1/sandboxes/sbx-one/proxy/44772') + await client.get('https://platform.test/v1/sandboxes/sbx-one/endpoints/44772') + assert len(calls) == 3 + + asyncio.run(run()) + + +def test_endpoint_retry_deadline_returns_final_failure(): + async def run(): + calls = [] + + async def serve(request): + calls.append(request) + return httpx.Response(503) + + transport = SandboxStartupTransport('https://platform.test/v1', lambda _: None, timeout=0, + transport=httpx.MockTransport(serve), retry_delay=0) + async with httpx.AsyncClient(transport=transport) as client: + response = await client.get('https://platform.test/v1/sandboxes/sbx-one/endpoints/44772') + assert response.status_code == 503 + assert len(calls) == 1 + + asyncio.run(run()) diff --git a/pyproject.toml b/pyproject.toml index ed654f6bb..1cbdca1ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,10 +16,16 @@ dependencies = [ harborrl = "harborrl.cli:main" [project.optional-dependencies] +sandbox = [ + "harbor==0.23.0; python_version >= '3.12'", + "opensandbox==0.1.9; python_version >= '3.12'", + "httpx>=0.27,<1", +] dev = [ "pytest>=8.0", "pytest-asyncio>=0.24", "ruff>=0.8", + "httpx>=0.27,<1", ] [tool.uv] @@ -31,6 +37,8 @@ exclude = ["runs*", ".venv*", "workspace*", "local*", "docs*", "tests*"] [tool.setuptools.package-data] "harborrl.platform" = ["*.sh"] +"harborrl.options" = ["resources/*.yaml"] +"harborrl.webui" = ["static/*"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/scripts/audit_public_tree.sh b/scripts/audit_public_tree.sh index 7f80ce653..b9c044203 100755 --- a/scripts/audit_public_tree.sh +++ b/scripts/audit_public_tree.sh @@ -4,11 +4,40 @@ set -euo pipefail ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" -python3 - <<'PY' +python3 - "$@" <<'PY' +import argparse from pathlib import Path import re import subprocess +parser = argparse.ArgumentParser(description="Audit public files and optional new-commit document history.") +parser.add_argument("--history-base", help="Check every commit after this ancestor, including deleted documents.") +args = parser.parse_args() + +public_documents = { + "CONTRIBUTING.md", "NOTICE.md", "README.md", "README_zh.md", "SECURITY.md", + "configs/harbor_hub/public_compatibility_summary.md", + "configs/harbor_hub/public_compatibility_summary_zh.md", + "docs/agents/environment-check.md", "docs/configuration-wizard.md", + "docs/deployment.md", "docs/sandbox-rollout.md", + "docs/train-inference-consistency.md", "docs/web-console.md", + "practices/h_cluster/README.md", "harborrl/webui/static/lucide-LICENSE.txt", +} + + +def audit_documents(names, location): + for name in names: + path = Path(name) + if path.suffix.lower() not in {".md", ".rst", ".adoc", ".txt"}: + continue + task_instruction = ( + path.parts[:3] == ("examples", "native", "tasks") + and path.name == "instruction.md" + ) + if name not in public_documents and not task_instruction: + raise SystemExit(f"document is not approved for publication ({location}): {name}") + + root = Path(".") files = subprocess.check_output( ["git", "ls-files", "-z"], text=True @@ -16,6 +45,23 @@ files = subprocess.check_output( files = [Path(name) for name in files if name] if not files: raise SystemExit("tracked tree is empty") +audit_documents([path.as_posix() for path in files], "index") + +if args.history_base: + if subprocess.run( + ["git", "merge-base", "--is-ancestor", args.history_base, "HEAD"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + ).returncode: + raise SystemExit("history base must resolve to an ancestor of HEAD") + commits = subprocess.check_output( + ["git", "rev-list", f"{args.history_base}..HEAD"], text=True + ).splitlines() + for commit in commits: + names = subprocess.check_output( + ["git", "ls-tree", "-r", "--name-only", "-z", commit], text=True + ).split("\0") + audit_documents([name for name in names if name], commit[:12]) + print(f"public document history audit passed: {len(commits)} new commits") forbidden_directories = {"backends", "benchmarks", "deploy", "runs", "tools"} for path in files: @@ -36,10 +82,11 @@ internal_patterns = { "personal path": re.compile(rb"/(mnt/shared-storage-user|home/[A-Za-z0-9_.-]+|root)/"), "internal DNS": re.compile(rb"(?i)[a-z0-9.-]+\.(local|internal|svc)\b"), "private registry": re.compile(rb"(?i)(registry\.[a-z0-9.-]+/(?:[a-z0-9_.-]+/){2,})"), + "cluster DNS": re.compile(rb"(?i)[a-z0-9.-]+\.(?:pjlab|p\x6a-lab)\.org\.cn\b"), } text_suffixes = { ".cfg", ".env", ".github", ".in", ".json", ".md", ".py", ".sh", ".toml", - ".txt", ".yaml", ".yml", + ".txt", ".yaml", ".yml", ".lock", } for path in files: if path.suffix.lower() not in text_suffixes and path.name != "Dockerfile": diff --git a/scripts/collect_service_evidence.py b/scripts/collect_service_evidence.py new file mode 100755 index 000000000..fa55d4add --- /dev/null +++ b/scripts/collect_service_evidence.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Collect service-mode run evidence for the controller-placement validation. + +Reads one run directory and reports: exit code, policy versions, checkpoints, +rollout groups/slots (from the RolloutService artifacts), RL_READY counts, +rewards, trace seals, integrity errors, and cleanup records. Machine-readable +JSON goes to stdout; exit 0 only when the mandatory gates hold. +""" +from __future__ import annotations + +import json +import sys +from pathlib import Path + + +def read_json(path): + return json.loads(Path(path).read_text()) + + +def main(run_dir): + run_dir = Path(run_dir) + report = {"run_dir": str(run_dir), "gates": {}} + + exit_code = int((run_dir / "exit-code").read_text().strip()) + report["exit_code"] = exit_code + report["gates"]["exit_code"] = exit_code == 0 + + # Policy versions: the manager's activation publishes policy-pool.json + # (snapshot) and each completed group records its admitted version. + versions = [] + snapshot = run_dir / "policy-pool.json" + final_version = None + if snapshot.is_file(): + final_version = read_json(snapshot).get("weight_version") + for result in sorted(run_dir.glob("rollout-service/groups/*/group-*/result.json")): + versions.append(read_json(result)["policy_version"]) + report["policy_versions"] = {"groups": versions, "final_pool": final_version} + report["gates"]["policy_advanced"] = len(set(versions)) >= 2 and ( + final_version is not None and str(final_version) == str(versions[-1])) + + checkpoints = sorted(p.name for p in run_dir.glob("checkpoints/*/iter_*")) \ + if run_dir.joinpath("checkpoints").is_dir() else [] + report["checkpoints"] = checkpoints + report["gates"]["checkpoints_written"] = len(checkpoints) >= 2 + + # Service artifacts: groups executed by the manager. + groups_root = run_dir / "rollout-service" / "groups" + groups = {} + if groups_root.is_dir(): + for run_id_dir in groups_root.iterdir(): + for group_dir in run_id_dir.iterdir(): + summary = {"group_id": group_dir.name, "states": [], + "has_result": (group_dir / "result.json").is_file()} + for state in ("running", "result", "cancelled", "failed"): + if (group_dir / f"{state}.json").is_file(): + summary["states"].append(state) + groups[group_dir.name] = summary + report["service_groups"] = groups + report["gates"]["service_groups_complete"] = bool(groups) and all( + g["states"] and "result" in g["states"] and g["has_result"] + for g in groups.values()) + + # Native rollouts + traces. + attempts = list((run_dir / "native-rollouts").rglob("trajectory.v2.json")) \ + if (run_dir / "native-rollouts").is_dir() else [] + readiness, rewards, integrity, seals = {}, [], 0, {"sealed": 0, "unsealed": 0} + turns_total = 0 + for path in attempts: + ir = read_json(path) + readiness[ir["readiness"]] = readiness.get(ir["readiness"], 0) + 1 + integrity += len(ir.get("integrity_errors", [])) + rewards.append(ir["evaluation"]["training_reward"]) + turns_total += len(ir["turns"]) + if ir["trace_seal"]["status"] == "sealed" and not ir["trace_seal"]["errors"]: + seals["sealed"] += 1 + else: + seals["unsealed"] += 1 + report["trajectories"] = { + "count": len(attempts), "readiness": readiness, "rewards": rewards, + "turns_total": turns_total, "integrity_errors": integrity, "seals": seals} + report["gates"]["all_rl_ready"] = len(attempts) >= 6 and readiness.get("RL_READY") == len(attempts) + report["gates"]["zero_integrity_errors"] = integrity == 0 + report["gates"]["zero_unsealed"] = seals["unsealed"] == 0 + + cleanup = [p.name for p in run_dir.rglob("*cleanup-required.json")] + unsealed = [p.name for p in run_dir.rglob("*trace-unsealed.json")] + failed = [p.name for p in run_dir.rglob("*failure.json")] + report["cleanup_records"] = {"cleanup_required": cleanup, "trace_unsealed": unsealed, + "failures": failed} + report["gates"]["clean_run"] = not (cleanup or unsealed or failed) + + passed = int(exit_code) == 0 and all(report["gates"].values()) + report["summary"] = ("PASS" if passed else "FAIL") + \ + f" gates={report['gates']}" + print(json.dumps(report, indent=2, ensure_ascii=False)) + return 0 if passed else 1 + + +if __name__ == "__main__": + if len(sys.argv) != 2: + raise SystemExit("usage: collect_service_evidence.py RUN_DIR") + raise SystemExit(main(sys.argv[1])) diff --git a/scripts/readiness_dial.py b/scripts/readiness_dial.py new file mode 100755 index 000000000..7268c4563 --- /dev/null +++ b/scripts/readiness_dial.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Live cross-machine readiness dials for the controller placement validation. + +Starts a temporary control plane + a /readyz listener on the manager, then +dials them from the GPU target and the rollout worker over SSH — the same +hops the real deployment requires (gpu_to_controller, worker_to_gateway). +""" +from __future__ import annotations + +import ipaddress +import json +import subprocess +import sys +import threading +import time +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +from harborrl.rollout.service import RolloutService, make_control_server + + +def ssh(spec, command, timeout=20): + result = subprocess.run( + ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", + "-o", "StrictHostKeyChecking=accept-new", spec, command], + capture_output=True, text=True, timeout=timeout, check=False) + return result.returncode == 0, (result.stdout + result.stderr).strip()[-200:] + + +def main(gpu_spec, worker_spec, manager_address): + manager_address = str(ipaddress.IPv4Address(manager_address)) + # 1) a real control plane on the manager's control port + service = RolloutService(run_root="/tmp/harborrl-readiness") + control = make_control_server(service, host="0.0.0.0", port=49200) + control_thread = threading.Thread(target=control.serve_forever, daemon=True) + control_thread.start() + # 2) a plain /readyz listener standing in for the gateway port + class Gateway(ThreadingHTTPServer): + daemon_threads = True + + class Handler(BaseHTTPRequestHandler): + def log_message(self, *args): + pass + + def do_GET(self): + body = b'{"ok": true}' + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + gateway = Gateway(("0.0.0.0", 49184), Handler) + gateway_thread = threading.Thread(target=gateway.serve_forever, daemon=True) + gateway_thread.start() + time.sleep(1) + + checks = [] + checks.append(("manager.control_plane_listening", + *ssh("localhost", "true"))) # manager-local sanity + # Direct local health check of the control plane. + local = subprocess.run( + ["python3", "-c", + "import urllib.request;urllib.request.urlopen('http://127.0.0.1:49200/healthz',timeout=5)"], + capture_output=True, text=True, timeout=10, check=False) + checks.append(("manager.control_healthz_local", local.returncode == 0, + "healthz answered on the manager")) + checks.append(("gpu_to_controller", + *ssh(gpu_spec, + "python3 -c \"import urllib.request;" + f"urllib.request.urlopen('http://{manager_address}:49200/healthz'," + "timeout=10)\""))) + checks.append(("worker_to_gateway", + *ssh(worker_spec, + "python3 -c \"import urllib.request;" + f"urllib.request.urlopen('http://{manager_address}:49184/readyz'," + "timeout=10)\""))) + + control.shutdown() + gateway.shutdown() + report = [{"check": name, "ok": ok, "detail": detail} + for name, ok, detail in checks] + passed = all(item["ok"] for item in report) + print(json.dumps({"readiness": report, + "summary": "PASS" if passed else "FAIL"}, indent=2)) + return 0 if passed else 1 + + +if __name__ == "__main__": + if len(sys.argv) != 4: + raise SystemExit("usage: readiness_dial.py GPU_SSH_SPEC WORKER_SSH_SPEC MANAGER_IPV4") + raise SystemExit(main(sys.argv[1], sys.argv[2], sys.argv[3])) diff --git a/scripts/rollout_service_smoke.py b/scripts/rollout_service_smoke.py new file mode 100755 index 000000000..c0de2e1a9 --- /dev/null +++ b/scripts/rollout_service_smoke.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +"""CPU smoke for the RolloutService control plane (no GPU, no Harbor, no SSH). + +Starts a real control server on loopback with a synthetic executor, then +drives the full lifecycle through the production HTTP client: register, +policy gate, activate, idempotent submit, conflict rejection, digest +validation, cancel, drain. Exits 0 only when every contract holds. +""" +from __future__ import annotations + +import asyncio +import json +import tempfile +import threading +from pathlib import Path +from types import SimpleNamespace + +from harborrl.backends.slime_v032.rollout_service_client import ( + RolloutServiceClient, + ServiceRolloutError, + load_group_result, +) +from harborrl.rollout.harbor_job.collector import collect +from harborrl.rollout.service import ( + RolloutService, + ServiceError, + make_control_server, + manifest_path_from, +) +from harborrl.trajectories.native import ( + Identity, + RewardProfile, + assemble, + digest, + publish, +) + +SAMPLING = {"temperature": 1.0, "top_p": 1.0, "top_k": -1, + "max_new_tokens": 64, "stop": []} +TASK = {"id": "smoke-task", "revision": "1", "path": "/task", + "task_digest": "d" * 64, + "reward_profile": {"key": "reward", "scale": 1.0, "offset": 0.0, + "raw_range": [0.0, 1.0]}} +RUN_ID = "smoke-run-1" + + +def step(name, ok, detail=""): + status = "PASS" if ok else "FAIL" + print(f"[{status}] {name}" + (f" — {detail}" if detail else "")) + return ok + + +def identity(slot, *, group_id, policy_version): + return Identity(run_id=RUN_ID, batch_id="rollout-0", group_id=group_id, + slot_id=slot, attempt_id=f"attempt-{policy_version}-{slot}", + trajectory_id=f"trajectory-{policy_version}-{slot}", + task_digest=TASK["task_digest"], + policy_version=policy_version, harness_digest="claude", + reward_digest=RewardProfile().digest, + sampling_digest=digest(SAMPLING)) + + +def synthetic_ir(root, slot, reward, *, group_id, policy_version): + ident = identity(slot, group_id=group_id, policy_version=policy_version) + attempt_root = root / "receipts" / ident.attempt_id + attempt_root.mkdir(parents=True, exist_ok=True) + result = {"id": "trial-" + ident.attempt_id, + "finished_at": "2026-10-21T00:00:00Z", + "verifier_environment_mode": "shared", + "verifier_result": {"rewards": {"reward": reward}}} + (attempt_root / "result.json").write_text(json.dumps(result)) + (attempt_root / "reward.json").write_text(json.dumps({"reward": reward})) + evaluation = collect(ident, result["id"], attempt_root / "result.json", + attempt_root, RewardProfile(), evidence_kind="synthetic") + trial = evaluation["harbor_trial_id"] + turn = {"identity": ident.to_dict(), "harbor_trial_id": trial, + "request_id": f"r-{slot}", "response_id": f"m-{slot}", + "engine_id": "engine", "input_ids": [1, 2], "output_ids": [3, 4], + "logprobs": [-0.2, -0.3], "logprob_semantics": "raw_model", + "policy_version": policy_version, "tokenizer_digest": "tok", + "template_digest": "template", "delivery": "consumed_confirmed", + "consumption_ref": "session#digest", "role": "policy", + "sampling": SAMPLING, "client_request": {"model": "policy"}, + "serving_input": {"input_ids": [1, 2]}, + "response": {"content": [{"type": "text", "text": "done"}]}, + "finish_reason": "end_turn", "evidence_kind": "synthetic"} + for key in ("client_request", "serving_input", "response"): + turn[key + "_digest"] = digest(turn[key]) + seal = {"identity": ident.to_dict(), "harbor_trial_id": trial, + "pending_requests": 0, "status": "sealed", + "request_ids": [turn["request_id"]], "turns_digest": digest([turn]), + "errors": []} + return assemble(ident, trial, [turn], seal, evaluation) + + +class Registry: + def __init__(self): + self.version, self.accepting = None, False + + def drain(self, timeout): + self.accepting = False + + def publish_version(self, states, expected): + self.version, self.accepting = str(expected), True + + +class Runtime: + def __init__(self, root): + self.registry, self.rollout_root = Registry(), root + + def manifest_path(self, ident): + return manifest_path_from(self.rollout_root, ident) + + def close(self): + pass + + +def group_request(group_index, *, policy_version="1", slots=2): + return {"group_id": f"group-{group_index}", "rollout_id": 0, + "group_index": group_index, "policy_version": policy_version, + "task": TASK, "sampling": dict(SAMPLING), + "slots": [{"slot_id": str(slot)} for slot in range(slots)]} + + +def main(): + root = Path(tempfile.mkdtemp(prefix="harborrl-service-smoke-")) + executions = [] + + async def executor(config, runtime_, request): + executions.append(request["group_id"]) + if request["group_index"] >= 1: + while True: # later groups block until they are cancelled + await asyncio.sleep(0.05) + irs = [] + for descriptor, reward in zip(request["slots"], (1.0, 0.0)): + ir = synthetic_ir(root, descriptor["slot_id"], reward, + group_id=request["group_id"], + policy_version=request["policy_version"]) + publish(runtime_.manifest_path(ir["identity"]), ir) + irs.append(ir) + return irs + + runtime = Runtime(root) + service = RolloutService(run_root=root, executor=executor, allow_synthetic=True, + group_wait_timeout=10) + # The smoke owns the service process, so it registers with its own token + # instead of consuming the one-time generated token. + service.register_run(RUN_ID, {"run_id": RUN_ID, "group_size": 2, + "purpose": "cpu service smoke"}, + token="smoke-token") + service.attach_runtime(RUN_ID, SimpleNamespace(drain_timeout=1), runtime) + control = make_control_server(service, host="127.0.0.1", port=0) + thread = threading.Thread(target=control.serve_forever, daemon=True) + thread.start() + url = f"http://127.0.0.1:{control.server_address[1]}" + client = RolloutServiceClient(url, RUN_ID, "smoke-token", timeout=10, + poll_interval=0.05) + + results = [] + try: + results.append(step("healthz answers", client.health()["ok"])) + # Register is idempotent by manifest digest. + replay = service.register_run(RUN_ID, {"run_id": RUN_ID, + "group_size": 2, + "purpose": "cpu service smoke"}) + results.append(step("register replay is idempotent", replay["existed"])) + try: + service.register_run(RUN_ID, {"run_id": RUN_ID, "other": 1}) + results.append(step("manifest conflict rejected", False)) + except ServiceError as exc: + results.append(step("manifest conflict rejected", exc.status == 409)) + + # Auth: a wrong token never reaches group endpoints. + bad = RolloutServiceClient(url, RUN_ID, "wrong", timeout=10) + try: + bad.group_status("group-0") + results.append(step("bad token rejected", False)) + except ServiceRolloutError as exc: + results.append(step("bad token rejected", exc.status == 401)) + + # Policy gate: nothing runs before activation. + try: + client.submit_group(group_request(0)) + results.append(step("submit before activate rejected", False)) + except ServiceRolloutError as exc: + results.append(step("submit before activate rejected", + exc.code == "stale_policy")) + + states = [{"endpoint": f"http://engine-{i}", "healthy": True, + "weight_version": "1"} for i in range(2)] + activation = client.activate_policy(states, "1") + results.append(step("policy activation opens admission", + activation["policy_version"] == "1" + and runtime.registry.accepting)) + pool = json.loads((root / "policy-pool.json").read_text()) + results.append(step("policy pool published to shared run dir", + pool["weight_version"] == "1")) + + request = group_request(0) + first = client.submit_group(request) + results.append(step("group completes with two slots", + first["status"] == "complete" + and len(first["result"]["slots"]) == 2)) + assert client.group_status("group-0")["status"] == "complete" + again = client.submit_group(json.loads(json.dumps(request))) + results.append(step("resubmit is idempotent", + again["result"]["request_digest"] + == first["result"]["request_digest"] + and executions.count("group-0") == 1, + f"executions={executions}")) + conflicting = group_request(0) + conflicting["sampling"]["temperature"] = 0.5 + try: + client.submit_group(conflicting) + results.append(step("same id different content rejected", False)) + except ServiceRolloutError as exc: + results.append(step("same id different content rejected", + exc.code == "conflict")) + + trajectories = load_group_result( + RUN_ID, request, first["result"], + allow_synthetic=any(slot["readiness"] != "RL_READY" + for slot in first["result"]["slots"])) + results.append(step("client validates manifests and digests", + [ir["identity"]["slot_id"] for ir in trajectories] + == ["0", "1"])) + export_path = Path(first["result"]["group_export"]["path"]) + export = json.loads(export_path.read_text()) + results.append(step("group export lands on shared storage", + export["trajectory_count"] == 2 + and export["mean"] == 0.5, str(export_path.relative_to(root)))) + + # Cancel a group that is still running, then stop admitting. + slow = group_request(1) + service.group_wait_timeout = 0.2 + pending = service.submit_group(RUN_ID, slow) + results.append(step("timeout answer says running and points at status", + pending["status"] in ("queued", "running") + and "hint" in pending)) + cancelled = client.cancel_group("group-1") + results.append(step("running group cancels", + cancelled["status"] == "cancelled")) + + client.drain() + try: + client.submit_group(group_request(2)) + results.append(step("drain closes admission", False)) + except ServiceRolloutError as exc: + results.append(step("drain closes admission", + exc.code == "admission_closed")) + finally: + control.shutdown() + control.server_close() + thread.join(5) + service.close() + + print(f"\nrun root: {root}") + print(f"{sum(results)}/{len(results)} checks passed") + return 0 if all(results) else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/sandbox_sdk_contract.py b/scripts/sandbox_sdk_contract.py new file mode 100644 index 000000000..9957d6ee7 --- /dev/null +++ b/scripts/sandbox_sdk_contract.py @@ -0,0 +1,60 @@ +"""Check the actual optional SDK/Harbor imports without allocating resources.""" +import asyncio +import inspect +import json +import tempfile +from pathlib import Path +from types import SimpleNamespace + +from harbor.models.trial.config import TrialConfig +from opensandbox import Sandbox + +from harborrl.harnesses.offline_claude import OfflineClaudeCode +from harborrl.rollout.harbor_job.environments.opensandbox import ( + ManagedOpenSandboxEnvironment, +) +from harborrl.trajectories.native import read_json + +sdk = ManagedOpenSandboxEnvironment._load_opensandbox(None) +assert {'extensions', 'skip_health_check', 'ready_timeout'} <= inspect.signature(Sandbox.create).parameters.keys() +assert 'import_path' in TrialConfig.model_fields['environment'].annotation.model_fields +assert not inspect.isabstract(OfflineClaudeCode) + + +async def cleanup_contract(): + with tempfile.TemporaryDirectory() as directory: + env = object.__new__(ManagedOpenSandboxEnvironment) + env._startup_transport = None + env.cfg = {'cleanup_timeout_s': 1} + env.evidence = Path(directory) + env.binding = {'image': 'image-test'} + env.record = {'sandbox_id': 'sandbox-test', 'runtime_digest': 'test', 'events': []} + calls = [] + + async def kill(): + calls.append('kill') + + async def close(): + calls.append('close') + + env._sandbox = SimpleNamespace(kill=kill, close=close) + await env.stop() + assert calls == ['kill', 'close'] + assert read_json(env.evidence / 'sandbox-lifecycle.json')['state'] == 'deleted' + + async def failed_kill(): + raise RuntimeError('secret-must-not-appear') + + env._sandbox = SimpleNamespace(kill=failed_kill, close=close) + try: + await env.stop() + except RuntimeError as exc: + assert str(exc) == 'Sandbox deletion failed' + else: + raise AssertionError('deletion failure was swallowed') + assert 'secret-must-not-appear' not in (env.evidence / 'cleanup-required.json').read_text() + assert read_json(env.evidence / 'sandbox-lifecycle.json')['state'] == 'delete_failed' + + +asyncio.run(cleanup_contract()) +print(json.dumps({'sdk_symbols': sorted(sdk), 'agent': OfflineClaudeCode.__name__, 'status': 'passed'})) diff --git a/scripts/sandbox_smoke.py b/scripts/sandbox_smoke.py new file mode 100644 index 000000000..c297c381d --- /dev/null +++ b/scripts/sandbox_smoke.py @@ -0,0 +1,65 @@ +"""Explicit single-instance live probe. Uses real quota and always attempts deletion.""" +import argparse +import asyncio +import json +import shlex +from pathlib import Path +from urllib.parse import urlsplit + +from harbor.models.task.config import EnvironmentConfig +from harbor.models.trial.paths import TrialPaths + +from harborrl.rollout.harbor_job.sandbox_config import environment_class, load_sandbox +from harborrl.rollout.harbor_job.sandbox_probe import check +from harborrl.trajectories.native import publish + + +async def run(args): + cfg = load_sandbox(args.config) + check(cfg) + root = Path(args.output).resolve() + root.mkdir(parents=True, exist_ok=False) + task = root / 'environment' + task.mkdir() + binding = cfg['bindings'][0] + adapter = environment_class(cfg) + env = adapter( + environment_dir=task, environment_name='sandbox-smoke', session_id=root.name, + trial_paths=TrialPaths(root / 'trial'), task_env_config=EnvironmentConfig(docker_image=binding['image']), + sandbox_config=cfg, binding=binding, evidence_dir=str(root), startup_dir=str(root / 'startup'), + gateway_url=args.gateway) + results = {} + try: + await env.start(False) + ok = await env.exec('printf sandbox-ok') + assert ok.return_code == 0 and ok.stdout == 'sandbox-ok', 'positive command failed' + negative = await env.exec('exit 7') + assert negative.return_code == 7, 'negative command exit code lost' + source = root / 'source.bin' + source.write_bytes(b'harborrl\x00sandbox\xff\n') + await env.upload_file(source, '/tmp/harborrl-smoke.bin') + await env.download_file('/tmp/harborrl-smoke.bin', root / 'download.bin') + assert source.read_bytes() == (root / 'download.bin').read_bytes(), 'file roundtrip failed' + agent_gateway = adapter.agent_gateway(cfg, args.gateway) + gateway = urlsplit(agent_gateway) + if gateway.scheme not in ('http', 'https') or not gateway.hostname or gateway.path not in ('', '/'): + raise ValueError('gateway must be an HTTP origin') + command = f'curl -sS -o /dev/null --connect-timeout 10 --max-time 20 {shlex.quote(agent_gateway)}' + connected = await env.exec(command) + publish(root / 'gateway-probe.json', {'exit_code': connected.return_code, + 'stdout': connected.stdout, 'stderr': connected.stderr}) + assert connected.return_code == 0, 'Sandbox cannot reach Gateway HTTP endpoint' + results.update(command=True, negative_exit=True, files=True, gateway_http=True) + finally: + await env.stop(True) + results['deleted'] = True + publish(root / 'smoke-result.json', results) + print(json.dumps(results)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('config') + parser.add_argument('--output', required=True) + parser.add_argument('--gateway', required=True) + asyncio.run(run(parser.parse_args())) diff --git a/scripts/sync_options_manifest.py b/scripts/sync_options_manifest.py new file mode 100644 index 000000000..ea0370ef3 --- /dev/null +++ b/scripts/sync_options_manifest.py @@ -0,0 +1,22 @@ +"""Generate the repository compatibility manifest from packaged metadata.""" +import argparse +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + source = ROOT / "harborrl/options/resources/datasets.yaml" + target = ROOT / "configs/harbor_hub/manifests.yaml" + expected = source.read_text(encoding="utf-8") + if args.check: + return int(target.read_text(encoding="utf-8") != expected) + target.write_text(expected, encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/harborrl/test_delivery_barrier.py b/tests/harborrl/test_delivery_barrier.py new file mode 100644 index 000000000..e6c0f0955 --- /dev/null +++ b/tests/harborrl/test_delivery_barrier.py @@ -0,0 +1,41 @@ +"""Delivery acknowledgements must precede native-session consumption audit.""" +import threading + +import pytest + +from harborrl.gateway.trace import TraceRegistry + + +def test_delivery_barrier_waits_and_wakes(tmp_path): + registry = TraceRegistry(tmp_path) + registry.attempts["attempt"] = {"pending": {"request"}, "turns": []} + entered = threading.Event() + finished = threading.Event() + + def wait(): + entered.set() + registry.wait_delivered("attempt", 2) + finished.set() + + worker = threading.Thread(target=wait) + worker.start() + assert entered.wait(1) + assert not finished.is_set() + with registry.changed: + registry.attempts["attempt"]["pending"].clear() + registry.attempts["attempt"]["turns"].append({"delivery": "sent"}) + registry.changed.notify_all() + worker.join(3) + assert finished.is_set() + assert not worker.is_alive() + + +def test_delivery_barrier_rejects_pending_and_unknown(tmp_path): + registry = TraceRegistry(tmp_path) + registry.attempts["attempt"] = {"pending": {"request"}, "turns": []} + with pytest.raises(TimeoutError, match="pending"): + registry.wait_delivered("attempt", 0) + registry.attempts["attempt"]["pending"].clear() + registry.attempts["attempt"]["turns"].append({"delivery": "delivery_unknown"}) + with pytest.raises(ValueError, match="not confirmed"): + registry.wait_delivered("attempt", 0) diff --git a/tests/harborrl/test_deployment_config.py b/tests/harborrl/test_deployment_config.py new file mode 100644 index 000000000..8e6eb9f46 --- /dev/null +++ b/tests/harborrl/test_deployment_config.py @@ -0,0 +1,176 @@ +"""Deployment schema v1: parsing, strictness, safety and defaults.""" +from __future__ import annotations + +import json + +import pytest + +from harborrl.deployment.schema import ( + DeploymentError, + embedded_local, + load_deployment, + summary, + validate, +) + + +def deployment_mapping(tmp_path, **overrides): + values = { + "schema_version": 1, + "manager": { + "id": "manager-a", "mode": "service", "bind": "0.0.0.0", + "control_port": 49200, "control_url": "http://manager-a:49200", + "gateway_port": 49184, "gateway_url": "http://manager-a:49184", + "run_token_file": str(tmp_path / "secrets" / "run-token"), + "source_dir": str(tmp_path / "manager-source"), + "controller_python": str(tmp_path / "manager-venv" / "bin" / "python"), + }, + "gpu_target": { + "id": "gpu-8x-h200", "transport": "ssh", + "ssh": {"host": "gpu-cluster-a"}, + "source_dir": str(tmp_path / "gpu-source"), + "train_python": str(tmp_path / "gpu-venv" / "bin" / "python"), + "environment_file": str(tmp_path / "gpu-env.json"), + "expected_gpus": 8, + }, + "storage": { + "mode": "shared_fs", "run_root": str(tmp_path / "runs"), + "manager_writable": True, "gpu_writable": True, "worker_writable": True, + }, + "rollout": { + "provider": "ssh_docker", + "workers": [ + {"id": "worker-a", "ssh": {"host": "worker-a"}, + "source_dir": str(tmp_path / "worker-source"), + "runner_python": str(tmp_path / "runner-venv" / "bin" / "python"), + "docker": True, "task_roots": [str(tmp_path / "tasks")]}, + {"id": "worker-b", "ssh": {"host": "worker-b", "port": 2222}, + "source_dir": str(tmp_path / "worker-source"), + "runner_python": str(tmp_path / "runner-venv" / "bin" / "python"), + "docker": True, "task_roots": [str(tmp_path / "tasks")]}, + ], + }, + "connectivity": { + "command_timeout_s": 30, "ssh_timeout_s": 15, "http_timeout_s": 10, + "gpu_to_controller": True, "worker_to_gateway": True, + "manager_to_gpu_inference": True, + }, + } + for section, fields in overrides.items(): + if section in values and isinstance(values[section], dict) and isinstance(fields, dict): + values[section].update(fields) + else: + values[section] = fields + return values + + +def test_valid_deployment_normalizes_and_summarizes(tmp_path): + config = validate(deployment_mapping(tmp_path)) + assert config["manager"]["control_url"] == "http://manager-a:49200" + assert config["rollout"]["workers"][1]["ssh"] == {"host": "worker-b", "port": 2222} + assert config["gpu_target"]["ssh"] == {"host": "gpu-cluster-a"} + plan_summary = summary(config, "deployment.yaml") + assert plan_summary["mode"] == "service" + assert plan_summary["gpu"]["transport"] == "ssh" + assert plan_summary["worker_ids"] == ["worker-a", "worker-b"] + assert plan_summary["config"] == config + assert summary(config, "deployment.yaml")["digest"] == plan_summary["digest"] + + +@pytest.mark.parametrize("mutation, message", [ + ({"manager": {"mode": "service", "control_url": "http://127.0.0.1:49200"}}, "loopback"), + ({"manager": {"control_url": "http://manager-a:49300"}}, "matching its declared port"), + ({"manager": {"gateway_port": 49200}}, "must differ"), + ({"manager": {"bind": ""}}, "bind"), + ({"gpu_target": {"transport": "local", "ssh": {"host": "gpu"}}}, "null for local"), + ({"gpu_target": {"transport": "rdma"}}, "transport"), + ({"gpu_target": {"expected_gpus": 0}}, "expected_gpus"), + ({"rollout": {"workers": []}}, "nonempty list"), + ({"connectivity": {"ssh_timeout_s": 0}}, "ssh_timeout_s"), + ({"storage": {"run_root": "relative/runs"}}, "absolute path"), +]) +def test_invalid_deployments_fail_closed(tmp_path, mutation, message): + with pytest.raises(DeploymentError, match=message): + validate(deployment_mapping(tmp_path, **mutation)) + + +def test_duplicate_worker_ids_and_secret_fields_rejected(tmp_path): + values = deployment_mapping(tmp_path) + values["rollout"]["workers"][1]["id"] = "worker-a" + with pytest.raises(DeploymentError, match="duplicate"): + validate(values) + values = deployment_mapping(tmp_path) + values["gpu_target"]["ssh"]["password"] = "hunter2" + with pytest.raises(DeploymentError, match="never stored"): + validate(values) + + +def test_service_mode_requires_reachable_urls_but_embedded_allows_loopback(tmp_path): + values = deployment_mapping(tmp_path, manager={"mode": "embedded"}) + values["manager"]["control_url"] = "http://127.0.0.1:49200" + values["manager"]["gateway_url"] = "http://127.0.0.1:49184" + assert validate(values)["manager"]["mode"] == "embedded" + + +def test_embedded_local_default_is_valid(): + config = embedded_local(run_root="/tmp/harborrl-runs", workers=("worker-a",)) + assert config["manager"]["mode"] == "embedded" + assert config["gpu_target"]["transport"] == "local" + assert config["rollout"]["provider"] == "embedded" + assert [worker["id"] for worker in config["rollout"]["workers"]] == ["worker-0"] + # Training-config SSH destinations are addresses, not ids: the embedded + # topology keeps slug ids while preserving the destination for access. + config = embedded_local(workers=("root@192.0.2.10", "worker-b")) + assert [worker["id"] for worker in config["rollout"]["workers"]] == [ + "worker-0", "worker-1"] + assert config["rollout"]["workers"][0]["ssh"]["host"] == "root@192.0.2.10" + + +def test_load_deployment_explicit_default_and_embedded(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + config, source = load_deployment() + assert source == "embedded-local" + assert config["manager"]["mode"] == "embedded" + + (tmp_path / "deployment.yaml").write_text( + json.dumps(deployment_mapping(tmp_path))) + config, source = load_deployment() + assert source == str(tmp_path / "deployment.yaml") + assert config["manager"]["mode"] == "service" + with pytest.raises(DeploymentError, match="not found"): + load_deployment(tmp_path / "missing.yaml") + + +def test_cli_dry_run_embeds_deployment_summary(tmp_path, capsys, monkeypatch): + from pathlib import Path + + from test_native_training import native_config + + config_path, task = native_config(tmp_path) + from harborrl.data.harbor.native_inspector import inspect_native + + catalog_path = Path( + json.loads(config_path.read_text().split("tasks: ", 1)[1].split("\n", 1)[0])["catalog"]) + catalog = json.loads(catalog_path.read_text()) + catalog[0]["task_digest"] = inspect_native(task)["task_digest"] + catalog_path.write_text(json.dumps(catalog)) + + deployment_file = tmp_path / "deployment.yaml" + deployment_file.write_text(json.dumps(deployment_mapping(tmp_path))) + + from harborrl.cli import main + + assert main(["train", str(config_path), "--deployment", str(deployment_file), + "--dry-run"]) == 0 + plan = json.loads(capsys.readouterr().out) + assert plan["deployment"]["mode"] == "service" + assert plan["deployment"]["gpu"]["transport"] == "ssh" + assert plan["deployment"]["source"] == str(deployment_file) + + # Without a deployment file the embedded-local topology is attached. + monkeypatch.chdir(tmp_path) + deployment_file.unlink() + assert main(["train", str(config_path), "--dry-run"]) == 0 + plan = json.loads(capsys.readouterr().out) + assert plan["deployment"]["mode"] == "embedded" + assert plan["deployment"]["source"] == "embedded-local" diff --git a/tests/harborrl/test_deployment_preflight.py b/tests/harborrl/test_deployment_preflight.py new file mode 100644 index 000000000..d7906c9be --- /dev/null +++ b/tests/harborrl/test_deployment_preflight.py @@ -0,0 +1,241 @@ +"""Deployment preflight graph: role-layered checks with faked SSH transport.""" +from __future__ import annotations + +import json +import socket +import subprocess +from pathlib import Path + +from test_deployment_config import deployment_mapping +from test_native_training import native_config + +from harborrl.deployment import preflight as deployment_preflight +from harborrl.deployment.schema import embedded_local, validate + + +def test_bindable_rejects_existing_listener(): + with socket.socket() as listener: + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(('127.0.0.1', 0)) + listener.listen() + ok, _ = deployment_preflight._bindable(*listener.getsockname()) + assert not ok + + +def test_bindable_allows_http_server_restart_after_connection_close(): + with socket.socket() as listener: + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(('127.0.0.1', 0)) + listener.listen() + address = listener.getsockname() + with socket.create_connection(address, timeout=2) as client: + accepted, _ = listener.accept() + with accepted: + accepted.settimeout(2) + accepted.shutdown(socket.SHUT_WR) + assert client.recv(1) == b'' + client.shutdown(socket.SHUT_WR) + assert accepted.recv(1) == b'' + ok, detail = deployment_preflight._bindable(*address) + assert ok, detail + + +def completed(returncode=0, stdout="", stderr=""): + return subprocess.CompletedProcess([], returncode, stdout, stderr) + + +def fake_remote(responses): + """Responses keyed by command prefix; unmatched shell tests succeed.""" + calls = [] + probe = json.dumps({"harbor_version": "0.23.0", "python": "3.12.13"}) + + def run_ssh(spec, command, *, timeout=30, connect_timeout=15): + calls.append((dict(spec), command)) + for prefix, result in responses.items(): + if command.startswith(prefix): + return completed(**result) if isinstance(result, dict) else result + if "runner --probe" in command: + return completed(stdout=probe) + if command.startswith("test "): + return completed(stdout="ok\n") # shell test protocol: && echo ok + return completed() + + return run_ssh, calls + + +def resolvable(deployment): + """Point service URLs at loopback so preflight resolution checks pass.""" + manager = deployment["manager"] + manager["control_url"] = f"http://127.0.0.1:{manager['control_port']}" + manager["gateway_url"] = f"http://127.0.0.1:{manager['gateway_port']}" + return deployment + + +def service_deployment(tmp_path): + return validate(deployment_mapping(tmp_path)) + + +def training_plan(tmp_path): + config_path, task = native_config(tmp_path) + from harborrl.config import load_config + from harborrl.data.harbor.native_inspector import inspect_native + + catalog_path = Path( + json.loads(config_path.read_text().split("tasks: ", 1)[1].split("\n", 1)[0])["catalog"]) + catalog = json.loads(catalog_path.read_text()) + catalog[0]["task_digest"] = inspect_native(task)["task_digest"] + catalog_path.write_text(json.dumps(catalog)) + return {"config": load_config(config_path)} + + +def by_name(checks, name): + return [check for check in checks if check.get("check") == name or check.get("path") == name] + + +def test_gpu_layer_probes_remote_machine_semantics(tmp_path, monkeypatch): + plan, deployment = training_plan(tmp_path), resolvable(service_deployment(tmp_path)) + (tmp_path / "runs").mkdir() + (tmp_path / "secrets").mkdir() + token = tmp_path / "secrets" / "run-token" + token.write_text("token\n") + token.chmod(0o600) + run_ssh, calls = fake_remote({ + "nvidia-smi": completed(stdout="0\n1\n2\n3\n4\n5\n6\n7\n"), + }) + monkeypatch.setattr(deployment_preflight.remote, "run_ssh", run_ssh) + checks = deployment_preflight.preflight(plan, deployment) + assert all(check["ok"] for check in checks), checks + gpu_checks = [check for check in checks if check["role"] == "gpu_target"] + assert {check["check"] for check in gpu_checks} >= { + "gpu_target.ssh", "gpu_target.source_dir", "gpu_target.train_python", + "gpu_target.expected_gpus"} + assert all(check["machine"] == "gpu-8x-h200" for check in gpu_checks) + # Model paths are evaluated on the GPU machine, not from the manager. + tested = [command for _, command in calls if "test -d" in command] + assert any("/model" in command for command in tested) + + +def test_gpu_layer_reports_shortage_and_unreachable_ssh(tmp_path, monkeypatch): + plan, deployment = training_plan(tmp_path), service_deployment(tmp_path) + (tmp_path / "runs").mkdir() + token = tmp_path / "secrets" / "run-token" + token.parent.mkdir() + token.write_text("token\n") + token.chmod(0o600) + run_ssh, _ = fake_remote({"nvidia-smi": completed(stdout="0\n1\n")}) + monkeypatch.setattr(deployment_preflight.remote, "run_ssh", run_ssh) + checks = deployment_preflight.preflight(plan, deployment) + shortage = by_name(checks, "gpu_target.expected_gpus") + assert shortage and not shortage[0]["ok"] + assert "visible GPUs" in shortage[0]["detail"] + + run_ssh, _ = fake_remote({"": completed(returncode=255, stderr="Permission denied")}) + monkeypatch.setattr(deployment_preflight.remote, "run_ssh", run_ssh) + checks = deployment_preflight.preflight(plan, deployment) + unreachable = by_name(checks, "gpu_target.ssh") + assert unreachable and not unreachable[0]["ok"] + assert unreachable[0]["role"] == "gpu_target" + assert "hint" in unreachable[0] + + +def test_worker_layer_validates_runner_runtime_and_docker(tmp_path, monkeypatch): + plan, deployment = training_plan(tmp_path), service_deployment(tmp_path) + (tmp_path / "runs").mkdir() + token = tmp_path / "secrets" / "run-token" + token.parent.mkdir() + token.write_text("token\n") + token.chmod(0o600) + probe = json.dumps({"harbor_version": "0.23.0", "python": "3.12.13"}) + run_ssh, _ = fake_remote({ + "docker info": completed(returncode=1, stderr="Cannot connect to the Docker daemon"), + "cd ": completed(stdout=probe), + }) + monkeypatch.setattr(deployment_preflight.remote, "run_ssh", run_ssh) + checks = deployment_preflight.preflight(plan, deployment) + docker = [check for check in checks if check["check"] == "worker.docker"] + assert len(docker) == 2 and not any(check["ok"] for check in docker) + assert all("Docker daemon" in check["hint"] for check in docker) + harbor = [check for check in checks if check["check"] == "worker.harbor_version"] + assert all(check["ok"] for check in harbor) + + run_ssh, _ = fake_remote({"cd ": completed(stdout=json.dumps( + {"harbor_version": "0.22.0", "python": "3.12.13"}))}) + monkeypatch.setattr(deployment_preflight.remote, "run_ssh", run_ssh) + checks = deployment_preflight.preflight(plan, deployment) + harbor = [check for check in checks if check["check"] == "worker.harbor_version"] + assert not any(check["ok"] for check in harbor) + # a wrong python version is also a failed runtime gate + run_ssh, _ = fake_remote({"cd ": completed(stdout=json.dumps( + {"harbor_version": "0.23.0", "python": "3.11.4"}))}) + monkeypatch.setattr(deployment_preflight.remote, "run_ssh", run_ssh) + checks = deployment_preflight.preflight(plan, deployment) + runtime = [check for check in checks if check["check"] == "worker.runner_runtime"] + assert not any(check["ok"] for check in runtime) + + +def test_manager_layer_checks_storage_token_and_ports(tmp_path): + plan = training_plan(tmp_path) + deployment = resolvable(service_deployment(tmp_path)) + checks = deployment_preflight.preflight(plan, deployment) + assert not by_name(checks, "storage.run_root")[0]["ok"] # run root missing + + (tmp_path / "runs").mkdir() + token = tmp_path / "secrets" / "run-token" + token.parent.mkdir() + token.write_text("token\n") + token.chmod(0o644) + checks = deployment_preflight.preflight(plan, deployment) + assert not by_name(checks, "manager.run_token_file")[0]["ok"] + token.chmod(0o600) + checks = deployment_preflight.preflight(plan, deployment) + assert by_name(checks, "manager.run_token_file")[0]["ok"] + assert by_name(checks, "manager.control_port")[0]["ok"] + + +def test_control_loop_layer_defers_live_dials_and_inference(tmp_path, monkeypatch): + plan, deployment = training_plan(tmp_path), service_deployment(tmp_path) + (tmp_path / "runs").mkdir() + token = tmp_path / "secrets" / "run-token" + token.parent.mkdir() + token.write_text("token\n") + token.chmod(0o600) + run_ssh, _ = fake_remote({}) + monkeypatch.setattr(deployment_preflight.remote, "run_ssh", run_ssh) + monkeypatch.setattr(deployment_preflight.socket, "getaddrinfo", + lambda host, port: [(None, None, None, None, (host, port))]) + checks = deployment_preflight.preflight(plan, deployment) + inference = by_name(checks, "connectivity.manager_to_gpu_inference") + assert inference and inference[0]["ok"] and "deferred" in inference[0]["detail"] + dials = [check for check in checks if check["check"].endswith(".dial")] + assert dials and all("deferred" not in check["detail"] or check["ok"] for check in dials) + + +def test_doctor_gates_local_checks_on_gpu_transport(tmp_path, monkeypatch): + from harborrl.platform import preflight as platform_preflight + + plan = training_plan(tmp_path) + plan["deployment"] = {"config": service_deployment(tmp_path), + "source": "deployment.yaml"} + run_ssh, _ = fake_remote({"nvidia-smi": completed(stdout="0\n" * 8)}) + monkeypatch.setattr(platform_preflight, "_deployment", + lambda p: p.get("deployment", {}).get("config")) + monkeypatch.setattr(deployment_preflight.remote, "run_ssh", run_ssh) + checks = platform_preflight.doctor(plan) + # Local GPU dependency probes do not run for a remote GPU target. + assert not any(check.get("dependency") == "ray" for check in checks) + assert not any(check.get("resource") == "visible GPU budget" for check in checks) + assert not any(check.get("service") == "external Harbor worker" for check in checks) + assert any(check.get("role") == "gpu_target" for check in checks) + + embedded = {"config": embedded_local(run_root=str(tmp_path / "runs")), + "source": "deployment.yaml"} + plan["deployment"] = embedded + checks = platform_preflight.doctor(plan) + assert any(check.get("dependency") == "ray" for check in checks) + assert any(check.get("resource") == "visible GPU budget" for check in checks) + + # The auto-derived embedded-local topology keeps the legacy check set. + plan["deployment"] = {"config": embedded_local(), "source": "embedded-local"} + legacy = platform_preflight.doctor(plan) + assert not any(check.get("role") == "gpu_target" for check in legacy) + assert any(check.get("dependency") == "ray" for check in legacy) diff --git a/tests/harborrl/test_deployment_targets.py b/tests/harborrl/test_deployment_targets.py new file mode 100644 index 000000000..ef609d9ad --- /dev/null +++ b/tests/harborrl/test_deployment_targets.py @@ -0,0 +1,235 @@ +"""TrainingTarget (P1): local child process and SSH launcher over shared storage.""" +from __future__ import annotations + +import json +import subprocess +import time + +import pytest + +from harborrl.deployment import training_target +from harborrl.deployment.training_target import ( + LocalTrainingTarget, + SshTrainingTarget, + TrainingTargetError, +) + + +def completed(returncode=0, stdout="", stderr=""): + return subprocess.CompletedProcess([], returncode, stdout, stderr) + + +def test_local_target_runs_and_forwards_exit_code(tmp_path): + target = LocalTrainingTarget(["bash", "-c", "exit 3"], cwd=str(tmp_path), env={"PATH": "/bin"}) + target.launch() + assert target.wait() == 3 + assert target.query()["state"] == "exited" + + +def test_local_target_cancel_interrupts(tmp_path): + target = LocalTrainingTarget(["bash", "-c", "trap '' INT; sleep 30"], + cwd=str(tmp_path), env={"PATH": "/bin"}) + target.launch() + time.sleep(0.2) + assert target.query()["state"] == "running" + target.cancel() + with pytest.raises(subprocess.TimeoutExpired): + target.child.wait(timeout=2) + import os + import signal + os.killpg(os.getpgid(target.child.pid), signal.SIGKILL) + target.child.wait() + + +def test_ssh_launcher_quotes_environment_and_captures_exit_code(tmp_path): + run_dir = tmp_path / "run" + run_dir.mkdir() + target = SshTrainingTarget({"host": "gpu"}, source_dir="/gpu/source", run_dir=run_dir) + launcher = target.write_launcher( + ["/manager/source/harborrl/platform/slime_train.sh", "--flag", "value with spaces"], + {"LR": "1e-6", "NOTE": "two spaces"}, + path_rewrites=[("/manager/source", "/gpu/source")]) + text = launcher.read_text() + assert "cd /gpu/source" in text + assert "/gpu/source/harborrl/platform/slime_train.sh" in text + assert "/manager/source" not in text + assert "export LR=1e-6" in text + assert "export NOTE='two spaces'" in text + assert "'value with spaces'" in text + assert "exit-code" in text + assert launcher.stat().st_mode & 0o111, "launcher must be executable" + + +def test_ssh_target_environment_file_is_loaded_via_python(tmp_path): + run_dir = tmp_path / "run" + run_dir.mkdir() + environment_file = tmp_path / "gpu-env.json" + environment_file.write_text(json.dumps({"SLIME_DIR": "/opt/slime"})) + target = SshTrainingTarget({"host": "gpu"}, source_dir="/gpu/source", run_dir=run_dir, + environment_file=environment_file) + launcher = target.write_launcher(["true"], {}) + text = launcher.read_text() + assert "HARBORRL_ENV_JSON" in text + assert "json.load" in text + + +def test_ssh_target_lifecycle_with_fake_remote(tmp_path, monkeypatch): + run_dir = tmp_path / "run" + run_dir.mkdir() + target = SshTrainingTarget({"host": "gpu", "port": 2222}, source_dir="/gpu/source", + run_dir=run_dir, poll_interval=0.05) + calls = [] + killed = {"done": False} + + def fake_run_ssh(spec, command, *, timeout=30, connect_timeout=15): + calls.append((dict(spec), command)) + if "setsid" in command: + (run_dir / "launcher.pid").write_text("4242\n") + return completed() + if "kill -0" in command: + return (completed(stdout="ok\n") if not killed["done"] + else completed(returncode=1)) + if command.startswith("kill -INT"): + killed["done"] = True + (run_dir / "exit-code").write_text("130\n") + return completed() + return completed() + + monkeypatch.setattr(training_target.remote, "run_ssh", fake_run_ssh) + target.write_launcher(["true"], {}) + target.launch() + with pytest.raises(TrainingTargetError): + target.launch() # double launch is refused + assert target.query() == {"state": "running", "exit_code": None, "pid": 4242, + "run_dir": str(run_dir)} + assert target.cancel() + assert target.wait() == 130 + assert target.query()["exit_code"] == 130 + # The ssh destination honors user/port from the normalized spec. + assert any(spec.get("port") == 2222 for spec, _ in calls), ( + "spec with port 2222 must reach run_ssh") + assert any("setsid" in command for _, command in calls) + + +def test_ssh_target_wait_timeout(tmp_path, monkeypatch): + run_dir = tmp_path / "run" + run_dir.mkdir() + target = SshTrainingTarget({"host": "gpu"}, source_dir="/gpu/source", run_dir=run_dir, + poll_interval=0.05) + + def fake_run_ssh(spec, command, *, timeout=30, connect_timeout=15): + if "setsid" in command: + (run_dir / "launcher.pid").write_text("99\n") + return completed() + + monkeypatch.setattr(training_target.remote, "run_ssh", fake_run_ssh) + target.write_launcher(["true"], {}) + target.launch() + with pytest.raises(TimeoutError): + target.wait(timeout=0.2) + + +def test_ssh_command_assembles_noninteractive_ssh(): + from harborrl.deployment.remote import ssh_command + + argv = ssh_command({"host": "gpu", "user": "root", "port": 2222, + "identity": "/key"}, "true", connect_timeout=7) + assert argv[0] == "ssh" + assert argv[1:7] == ["-o", "BatchMode=yes", "-o", "ClearAllForwardings=yes", + "-o", "StrictHostKeyChecking=accept-new"] + assert argv[7:9] == ["-o", "ConnectTimeout=7"] + assert argv[9:13] == ["-p", "2222", "-i", "/key"] + assert argv[-2:] == ["root@gpu", "true"] + + +def test_service_dispatch_launches_gpu_job_through_controller(tmp_path, monkeypatch, capsys): + """P1+P3 manager flow: materialize, register the run, launch, wait, report.""" + import json as json_module + from pathlib import Path + + from test_deployment_config import deployment_mapping + from test_native_training import native_config + + from harborrl.config import launch_plan, load_config + from harborrl.data.harbor.native_inspector import inspect_native + from harborrl.deployment import controller as controller_module + from harborrl.deployment.schema import summary, validate + + config_path, task = native_config(tmp_path) + catalog_path = Path(json_module.loads( + config_path.read_text().split("tasks: ", 1)[1].split("\n", 1)[0])["catalog"]) + catalog = json_module.loads(catalog_path.read_text()) + catalog[0]["task_digest"] = inspect_native(task)["task_digest"] + catalog_path.write_text(json_module.dumps(catalog)) + config = load_config(config_path) + config["output"]["root"] = str(tmp_path / "shared-runs") + plan = launch_plan(config) + + deployment = validate(deployment_mapping(tmp_path, storage={ + "run_root": str(tmp_path / "shared-runs")})) + manager = deployment["manager"] + token_file = Path(manager["run_token_file"]) + token_file.parent.mkdir(parents=True, exist_ok=True) + token_file.write_text("token\n") + token_file.chmod(0o600) + (tmp_path / "shared-runs").mkdir() + plan["deployment"] = summary(deployment, "deployment.yaml") + + launched = {} + + class FakeControl: + def __init__(self, service, host, port): + self.address = (host, port) + + def serve_forever(self): + pass + + def shutdown(self): + pass + + def server_close(self): + pass + + class FakeTarget: + def __init__(self, spec, *, source_dir, run_dir, **kwargs): + self.source_dir, self.run_dir = source_dir, Path(run_dir) + + def write_launcher(self, command, environment, *, path_rewrites=()): + launched["command"] = list(command) + launched["environment"] = dict(environment) + launched["rewrites"] = list(path_rewrites) + return self.run_dir / "launcher.sh" + + def launch(self): + launched["run_dir"] = str(self.run_dir) + (self.run_dir / "exit-code").write_text("0\n") + + def wait(self): + return 0 + + def cancel(self): + return True + + monkeypatch.setattr(controller_module, "make_control_server", FakeControl) + monkeypatch.setattr(controller_module, "SshTrainingTarget", FakeTarget) + monkeypatch.setattr(controller_module, 'check_gpu_control', lambda _: {'gpu_to_controller': True}) + + exit_code = controller_module.dispatch_service(plan, deployment, checks=[]) + assert exit_code == 0 + run_dir = Path(launched["run_dir"]) + assert run_dir.is_dir() + environment = launched["environment"] + assert environment["HARBORRL_ROLLOUT_MODE"] == "service" + assert environment["HARBORRL_ROLLOUT_SERVICE_URL"] == manager["control_url"] + assert environment['HARBORRL_ROLLOUT_SERVICE_TOKEN_FILE'] == str(token_file) + assert 'HARBORRL_ROLLOUT_SERVICE_TOKEN' not in environment + assert environment["HARBORRL_NATIVE_GATEWAY_URL"] == manager["gateway_url"] + assert json_module.loads(environment["HARBORRL_NATIVE_RUNNER_WORKERS"]) == [ + "worker-a", "worker-b"] + # The launcher runs inside the GPU checkout with a rewritten script path. + assert launched["rewrites"] == [(manager["source_dir"], + deployment["gpu_target"]["source_dir"])] + launch_record = json_module.loads((run_dir / "launch.json").read_text()) + assert launch_record["control_url"] == manager["control_url"] + effective = json_module.loads((run_dir / "effective-config.json").read_text()) + assert effective["components"]["execution_backend"] == "harbor_job" diff --git a/tests/harborrl/test_math_interfaces.py b/tests/harborrl/test_math_interfaces.py new file mode 100644 index 000000000..a17788fc1 --- /dev/null +++ b/tests/harborrl/test_math_interfaces.py @@ -0,0 +1,126 @@ +"""CPU contracts for the explicit MATH component interfaces.""" +import json +from dataclasses import replace + +import pytest + +from harborrl.math_registry import ( + BACKEND_CONTRACT, + list_components, + select_math, + validate_combination, +) +from harborrl.models.qwen import QwenModelAdapter +from harborrl.tasks.jsonl_provider import JsonlTaskProvider +from harborrl.tasks.provider import NativeTaskProvider + + +def math_config(): + return { + "execution": {"backend": "harbor_job"}, + "harness": {"name": "claude_code"}, + "harbor": {"version": "0.23.0"}, + "gateway": {"audited_raw_logprobs": True}, + "model": {"args_file": "qwen3-8B"}, + "training": {"backend_contract": BACKEND_CONTRACT}, + "sampling": {"group_size": 2}, + } + + +def test_registry_lists_versioned_default_components(): + components = list_components() + assert [spec["id"] for spec in components["models"]] == ["qwen"] + assert [spec["id"] for spec in components["algorithms"]] == ["grpo"] + assert [spec["id"] for spec in components["tasks"]] == ["harbor_jsonl", "native_harbor"] + assert [spec["id"] for spec in components["harnesses"]] == ["claude_code"] + for dimension in components.values(): + for spec in dimension: + assert spec["version"] + assert spec["evidence"] + assert spec["limitations"] + +def test_combination_checks_reject_incompatible_evidence_and_groups(): + config = math_config() + selection = select_math(config) + assert validate_combination(selection, config) is selection + + unaudited = {**config, "gateway": {"audited_raw_logprobs": False}} + with pytest.raises(ValueError, match="requires audited raw logprobs"): + validate_combination(selection, unaudited) + + larger_group = replace(selection.algorithm, minimum_group_size=3) + with pytest.raises(ValueError, match="requires group size"): + validate_combination(replace(selection, algorithm=larger_group), config) + + class Tokenizer: + def get(self, key): + assert key == "input_ids" + return [1, 2] + + def apply_chat_template(self, *args, **kwargs): + assert kwargs["enable_thinking"] is False + return self + + adapter = QwenModelAdapter() + converted = {"messages": [{"role": "user", "content": "run"}], "tools": []} + assert adapter.render_chat(Tokenizer(), converted) == [1, 2] + blocks = adapter.parse_output( + '{"name":"bash","arguments":{"command":"pwd"}}', + response_id="message", + tools=[{"function": {"name": "bash"}}], + ) + assert blocks[0]["type"] == "tool_use" + + +def test_jsonl_task_provider_is_selected_by_catalog_format(tmp_path): + task = tmp_path / "task" + (task / "environment").mkdir(parents=True) + (task / "tests").mkdir() + (task / "task.toml").write_text('version = "1.0"\n') + (task / "instruction.md").write_text("jsonl instruction\n") + (task / "environment/Dockerfile").write_text("FROM debian:bookworm\n") + (task / "tests/test.sh").write_text("true\n") + provider = JsonlTaskProvider() + digest = provider.inspect(task)["task_digest"] + catalog = tmp_path / "catalog.jsonl" + catalog.write_text(json.dumps({ + "id": "task", "revision": "1", "path": str(task), + "task_digest": digest, "reward_profile": {"key": "reward"}, + }) + "\n") + config = {**math_config(), "tasks": {"catalog": str(catalog)}} + selection = select_math(config) + assert selection.task.id == "harbor_jsonl" + entries = provider.catalog(config) + rows = provider.prompt_rows(entries) + assert rows[0]["task"] == "jsonl instruction\n" + assert rows[0]["metadata"]["task_provider"] == "harbor_jsonl" + + incompatible = {**config, "execution": {"backend": "process"}} + with pytest.raises(ValueError, match="does not support execution backend"): + validate_combination(select_math(incompatible), incompatible) + + catalog.write_text("{broken\n") + with pytest.raises(ValueError, match="invalid JSONL catalog line"): + provider.catalog(config) + + +def test_native_task_provider_locks_and_materializes_prompts(tmp_path): + task = tmp_path / "task" + (task / "environment").mkdir(parents=True) + (task / "tests").mkdir() + (task / "task.toml").write_text('version = "1.0"\n') + (task / "instruction.md").write_text("original instruction\n") + (task / "environment/Dockerfile").write_text("FROM debian:bookworm\n") + (task / "tests/test.sh").write_text("true\n") + provider = NativeTaskProvider() + digest = provider.inspect(task)["task_digest"] + catalog = tmp_path / "catalog.json" + catalog.write_text(json.dumps([{ + "id": "task", "revision": "1", "path": str(task), + "task_digest": digest, "reward_profile": {"key": "reward"}, + }])) + config = {**math_config(), "tasks": {"catalog": str(catalog)}} + entries = provider.catalog(config) + rows = provider.prompt_rows(entries) + assert rows[0]["task"] == "original instruction\n" + assert rows[0]["metadata"]["native_task"]["task_digest"] == digest diff --git a/tests/harborrl/test_native_gateway.py b/tests/harborrl/test_native_gateway.py index d3a6d5ffb..75ab7416e 100644 --- a/tests/harborrl/test_native_gateway.py +++ b/tests/harborrl/test_native_gateway.py @@ -65,9 +65,10 @@ def test_tool_roundtrip(): convert(p, model="policy") -def test_claude_2_1_220_control_turns_are_accepted_without_hidden_sampling(): +@pytest.mark.parametrize("effort", ["high", "xhigh"]) +def test_claude_2_1_control_turns_are_accepted_without_hidden_sampling(effort): converted = convert(payload( - output_config={"effort": "high"}, + output_config={"effort": effort}, thinking={"type": "adaptive"}, context_management={"edits": [{"type": "clear_thinking_20251015", "keep": "all"}]}, system=[{"type": "text", "text": "locked system prompt"}], @@ -239,6 +240,32 @@ def test_nonfinite_generated_tool_is_rejected(): response_id="m", tools=[{"function": {"name": "bash"}}]) +def test_generated_tool_errors_distinguish_name_and_arguments(): + tools = [{"function": {"name": "bash"}}] + with pytest.raises(ProtocolError, match="unknown generated tool"): + parse_qwen_output('{"name":"sh","arguments":{}}', + response_id="m", tools=tools) + with pytest.raises(ProtocolError, match="arguments are not an object"): + parse_qwen_output('{"name":"bash","arguments":[]}', + response_id="m", tools=tools) + + +def test_generated_tool_name_alias_is_canonicalized_only_when_unique(): + tools = [{"function": {"name": "Bash"}}] + output = parse_qwen_output('{"name":"bash","arguments":{"command":"pwd"}}', + response_id="m", tools=tools) + assert output[0]["name"] == "Bash" + with pytest.raises(ProtocolError, match="unknown generated tool"): + parse_qwen_output('{"name":"sh","arguments":{}}', + response_id="m", tools=tools) + + +def test_qwen_finish_marker_maps_to_empty_end_turn(): + output = parse_qwen_output('{"name":"finish","arguments":{}}', + response_id="m", tools=[{"function": {"name": "Bash"}}]) + assert output == [] + + def test_sglang_generation_version_and_logprob_evidence(): from harborrl.gateway.sglang_backend import SGLangBackend class Tokenizer: @@ -246,7 +273,11 @@ def apply_chat_template(self, *args, **kwargs): return [11, 12] def decode(self, token_ids, skip_special_tokens): return "done" - backend = SGLangBackend("http://engine", Tokenizer(), tokenizer_digest="tok", template_digest="tpl") + from harborrl.models.qwen import QwenModelAdapter + backend = SGLangBackend( + "http://engine", Tokenizer(), QwenModelAdapter(), + tokenizer_digest="tok", template_digest="tpl", + ) converted = convert(payload(), model="policy") serving = {"input_ids": backend.prepare(converted)} output = {"text": "done", "meta_info": {"weight_version": "1", "output_token_logprobs": [[-.2, 13]], @@ -293,7 +324,31 @@ def open(self, request, timeout): assert timeout == 30 return Response() - backend = SGLangBackend("http://engine", SimpleNamespace(), tokenizer_digest="tok", - template_digest="tpl", timeout=30) + from harborrl.models.qwen import QwenModelAdapter + backend = SGLangBackend( + "http://engine", SimpleNamespace(), QwenModelAdapter(), + tokenizer_digest="tok", template_digest="tpl", timeout=30, + ) backend.opener = Opener() assert backend.weight_version() == "7" + + +def test_native_gateway_thread_does_not_own_asyncio_default_executor(): + from harborrl.rollout.native_generate import NativeRolloutRuntime + + started, stopped = threading.Event(), threading.Event() + + class Server: + def serve_forever(self): + started.set() + stopped.wait(5) + + runtime = object.__new__(NativeRolloutRuntime) + runtime.server = Server() + runtime._start_gateway() + try: + assert started.wait(1) + assert runtime.thread.daemon is True + finally: + stopped.set() + runtime.thread.join(1) diff --git a/tests/harborrl/test_native_lifecycle.py b/tests/harborrl/test_native_lifecycle.py index bdc76b9ea..a36ef0fd9 100644 --- a/tests/harborrl/test_native_lifecycle.py +++ b/tests/harborrl/test_native_lifecycle.py @@ -35,7 +35,12 @@ def test_restart_cannot_reopen_persisted_attempt(tmp_path): def test_profile_controls_reach_agent_and_host(): config = claude_config(PROFILE, "http://gateway:8000", "token") + assert config["kwargs"]["disallowed_tools"] == "Agent,Task" + disabled = claude_config({**PROFILE, "disallowed_tools": "Agent,Task,WebSearch,WebFetch"}, + "http://gateway:8000", "token") + assert disabled["kwargs"]["disallowed_tools"] == "Agent,Task,WebSearch,WebFetch" assert config["env"]["DISABLE_AUTO_COMPACT"] == "1" + assert config["env"]["ANTHROPIC_MODEL"] == "policy" assert config["env"]["NO_PROXY"] == "gateway" assert config["env"]["no_proxy"] == "gateway" assert config["kwargs"]["version"] == "2.1.0" diff --git a/tests/harborrl/test_native_terminal.py b/tests/harborrl/test_native_terminal.py new file mode 100644 index 000000000..eb94c8a98 --- /dev/null +++ b/tests/harborrl/test_native_terminal.py @@ -0,0 +1,111 @@ +"""Regression coverage for the full coordinator success path and empty finals.""" +import asyncio +import json +import sys + +import pytest +from test_native_contracts import identity, make_ir +from test_native_lifecycle import PROFILE + +from harborrl.gateway.trace import TraceRegistry +from harborrl.harnesses.claude_code import ClaudeCodeHarnessAdapter +from harborrl.rollout.harbor_job.coordinator import run_attempt +from harborrl.trajectories.native import RewardProfile, digest + + +def test_casefold_tool_alias_must_be_unique(): + from harborrl.gateway.messages import ProtocolError, parse_qwen_output + with pytest.raises(ProtocolError, match="ambiguous"): + parse_qwen_output('{"name":"BASH","arguments":{}}', + response_id="r", tools=[{"function": {"name": "Bash"}}, + {"function": {"name": "bash"}}]) + + +@pytest.mark.parametrize("empty", [False, True]) +def test_coordinator_publishes_complete_success(tmp_path, monkeypatch, empty): + ident = identity() + trial_id = "trial-" + ident.attempt_id + root = tmp_path / "attempt" + trial = root / "runner/harbor/trial" + template = make_ir(tmp_path / "fixture", nturns=2) + + class Registry(TraceRegistry): + def register(self, ident, trial_id, **kwargs): + token = super().register(ident, trial_id, **kwargs) + turns = template["turns"] + for index, original in enumerate(turns): + aid, request, _, _ = self.begin(token) + turn = {**original, "request_id": request, "delivery": "sent"} + if empty and index == 1: + turn["response"] = {"content": []} + turn["response_digest"] = digest(turn["response"]) + self.finish(aid, request, turn) + self.delivery(aid, turn["response_id"], "sent") + session = trial / "agent/sessions/projects/session.jsonl" + session.parent.mkdir(parents=True) + session.write_text("\n".join(json.dumps({ + "type": "assistant", "sessionId": "session", "message": { + "id": turn["response_id"], "role": "assistant", + "content": turn["response"]["content"]}}) + for turn in turns[:1 if empty else 2])) + (trial / "agent/claude-code.txt").write_text(json.dumps({ + "type": "result", "subtype": "success", "is_error": False, + "result": "", "stop_reason": "end_turn", "session_id": "session", + "terminal_reason": "completed"})) + (trial / "verifier").mkdir() + (trial / "verifier/reward.json").write_text('{"reward": 1}') + (trial / "result.json").write_text(json.dumps({ + "id": trial_id, "finished_at": "2026-10-19T00:00:00Z", + "verifier_environment_mode": "shared", + "verifier_result": {"rewards": {"reward": 1}}})) + return token + + worker = tmp_path / "worker.py" + terminal = {"type": "terminal", "trial_id": trial_id, + "trial_dir": str(trial), "result_path": str(trial / "result.json")} + worker.write_text("import json, sys\njson.loads(sys.stdin.readline())\n" + f"print({json.dumps({'type': 'prepared', 'trial_id': trial_id})!r}, flush=True)\n" + "sys.stdin.readline()\n" + f"print({json.dumps(terminal)!r}, flush=True)\n") + spawn = asyncio.create_subprocess_exec + + async def fake_spawn(*args, **kwargs): + return await spawn(sys.executable, "-S", str(worker), **kwargs) + + monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_spawn) + ir = asyncio.run(run_attempt( + ident, task_path=tmp_path, profile=PROFILE, gateway_url="http://gateway", + runner_python=sys.executable, root=root, registry=Registry(tmp_path / "trace"), + reward_profile=RewardProfile())) + assert ir["readiness"] == "CONTRACT_READY" + assert json.loads((root / "trajectory.v2.json").read_text()) == ir + assert (root / "evaluation.json").is_file() + assert not (root / "cleanup-required.json").exists() + assert all(turn["delivery"] == "consumed_confirmed" for turn in ir["turns"]) + + +@pytest.mark.parametrize("change", [ + {"content": [{"type": "text", "text": "missing"}]}, + {"finish_reason": "length"}, {"is_final": False}, +]) +def test_empty_final_rejects_other_missing_responses(tmp_path, change): + class Registry: + def unconsumed_responses(self, aid): + return [{"response_id": "r", "content": [], "finish_reason": "end_turn", + "is_final": True, **change}] + with pytest.raises(ValueError, match="only one empty final"): + ClaudeCodeHarnessAdapter().audit_empty_final(tmp_path / "absent", Registry(), "a", {}) + + +@pytest.mark.parametrize("change", [{"session_id": "wrong"}, {"is_error": True}, + {"subtype": "error_max_turns"}, {"result": "nonempty"}, {"stop_reason": "max_tokens"}]) +def test_empty_final_requires_cli_consumption_evidence(tmp_path, change): + class Registry: + def unconsumed_responses(self, aid): + return [{"response_id": "r", "content": [], "finish_reason": "end_turn", "is_final": True}] + path = tmp_path / "claude-code.txt" + path.write_text(json.dumps({"type": "result", "subtype": "success", "is_error": False, + "result": "", "stop_reason": "end_turn", "session_id": "session", + "terminal_reason": "completed", **change})) + with pytest.raises(ValueError, match="matching session result"): + ClaudeCodeHarnessAdapter().audit_empty_final(path, Registry(), "a", {"session_id": "session"}) diff --git a/tests/harborrl/test_native_training.py b/tests/harborrl/test_native_training.py index 40ce2b565..ecfd6a24e 100644 --- a/tests/harborrl/test_native_training.py +++ b/tests/harborrl/test_native_training.py @@ -142,8 +142,19 @@ def test_cli_command_mode_selects_experiment_dimensions(tmp_path, capsys, monkey assert positional_plan["config"]["schema_version"] == 2 -def test_native_shell_dry_run_uses_official_v032_hooks(tmp_path): - native_config(tmp_path) +@pytest.mark.parametrize('overrides, expected_lr', [ + ([], '0.0001'), + (['training.learning_rate=0.000025'], '2.5e-05'), +]) +def test_native_shell_dry_run_uses_official_v032_hooks(tmp_path, overrides, expected_lr): + config_path, task = native_config(tmp_path) + from harborrl.data.harbor.native_inspector import inspect_native + config = load_config(config_path, overrides) + catalog_path = Path(config['tasks']['catalog']) + entries = json.loads(catalog_path.read_text()) + entries[0]['task_digest'] = inspect_native(task)['task_digest'] + catalog_path.write_text(json.dumps(entries)) + plan = launch_plan(config) prompt = tmp_path / "native.jsonl" prompt.write_text('{"task":"fixture","metadata":{"harborrl_native":true}}\n') root = tmp_path / "run" @@ -154,6 +165,7 @@ def test_native_shell_dry_run_uses_official_v032_hooks(tmp_path): (model_args / "qwen3-8B.sh").write_text("MODEL_ARGS=(--hidden-size 4096)\n") env = {key: value for key, value in os.environ.items() if key in {"PATH", "HOME", "USER", "LANG", "LC_ALL", "TMPDIR"}} + env.update(plan["environment"]) env.update({ "DRY_RUN": "1", "DATASET": "native", "ALGO": "grpo", "HARNESS_OPTION": "native-claude-code", "HARBORRL_NATIVE_ROLLOUT": "1", @@ -162,14 +174,16 @@ def test_native_shell_dry_run_uses_official_v032_hooks(tmp_path): "MODEL_TAG": "qwen3-8B", "NUM_GPUS": "8", "ACTOR_GPUS": "4", "ROLLOUT_GPUS": "4", "TP_SIZE": "4", "ROLLOUT_NUM_GPUS_PER_ENGINE": "4", "HARBORRL_GPU_LAYOUT": "split", "NUM_ROLLOUT": "1", "SAVE_INTERVAL": "1", - "ROLLOUT_BATCH_SIZE": "1", "N_SAMPLES": "2", "RUN_DIR": str(root), "RUN_ID": "run", + "RUN_DIR": str(root), "RUN_ID": "run", "RUNS_ROOT": str(tmp_path), "CKPT_ROOT": str(root / "checkpoints"), "HARBORRL_NATIVE_MAX_ATTEMPTS": "2", "HARBORRL_NATIVE_DRAIN_TIMEOUT": "1", "HARBORRL_NATIVE_RUNNER_WORKERS": '["worker-a", "worker-b"]', "HARBORRL_NATIVE_GATEWAY_HOST": "0.0.0.0", "HARBORRL_NATIVE_SLIME_CONTRACT": "slime-v032", "SLIME_DIR": str(slime), + "TRAIN_PYTHON": sys.executable, }) + env['LR'] = plan['environment']['LR'] script = Path(__file__).resolve().parents[2] / "harborrl/platform/slime_train.sh" output = tmp_path / "shell.log" with output.open("w") as stream: @@ -179,6 +193,24 @@ def test_native_shell_dry_run_uses_official_v032_hooks(tmp_path): assert result.returncode == 0, result.stdout command = next(line for line in result.stdout.splitlines() if line.startswith("[dry-run] ")) assert "--loss-type custom_loss" in command + import shlex + arguments = shlex.split(command.removeprefix('[dry-run] ')) + assert arguments[arguments.index('--lr') + 1] == expected_lr + expected_flags = { + '--num-rollout': '1', '--save-interval': '1', + '--rollout-batch-size': '1', '--n-samples-per-prompt': '2', + '--rollout-max-response-len': '128', '--rollout-max-context-len': '1024', + '--num-gpus-per-node': '8', '--actor-num-gpus-per-node': '4', + '--rollout-num-gpus': '4', '--tensor-model-parallel-size': '4', + '--rollout-num-gpus-per-engine': '4', '--hf-checkpoint': '/model', + '--ref-load': '/reference', '--prompt-data': str(prompt), + '--save': str(root / 'checkpoints' / 'run'), + } + for flag, value in expected_flags.items(): + assert arguments[arguments.index(flag) + 1] == value + recorded = json.loads(next((root / 'dry-run-commands').glob('*.json')).read_text()) + assert recorded['command'] == arguments + assert recorded['mode'] == 'dry_run' for argument in ( "--rollout-function-path harborrl.backends.slime_v032.rollout.generate_rollout", "--custom-convert-samples-to-train-data-path harborrl.backends.slime_v032.converter.convert_samples_to_train_data", @@ -208,96 +240,57 @@ def test_native_shell_dry_run_uses_official_v032_hooks(tmp_path): assert "--no-load-optim" in command -def test_native_runtime_binds_gateway_with_registry_and_backend(tmp_path, monkeypatch): - from harborrl.gateway import server as gateway_server - from harborrl.gateway import sglang_backend, trace - from harborrl.rollout import native_generate +def test_slime_runtime_config_maps_args_and_environment(tmp_path, monkeypatch): + from harborrl.backends.slime_v032 import rollout as slime_rollout + from harborrl.rollout.runtime import RolloutRuntimeConfig catalog = tmp_path / "catalog.json" catalog.write_text("[]") profile = tmp_path / "profile.json" profile.write_text(json.dumps({"model": "policy"})) - tokenizer = SimpleNamespace( - apply_chat_template=lambda *args, **kwargs: [1, 2], - get_vocab=lambda: {"token": 1}, - ) - tokenizer_digest, template_digest = native_generate.tokenizer_fingerprint(tokenizer) environment = { - "RUN_DIR": str(tmp_path), "HARBORRL_NATIVE_CATALOG": str(catalog), + "RUN_ID": "run-1", "RUN_DIR": str(tmp_path), + "HARBORRL_NATIVE_CATALOG": str(catalog), "HARBORRL_NATIVE_PROFILE": str(profile), - "HARBORRL_NATIVE_TOKENIZER_DIGEST": tokenizer_digest, - "HARBORRL_NATIVE_TEMPLATE_DIGEST": template_digest, "HARBORRL_NATIVE_GATEWAY_HOST": "127.0.0.1", "HARBORRL_NATIVE_GATEWAY_PORT": "49183", + "HARBORRL_NATIVE_GATEWAY_URL": "http://gateway:49183", + "HARBORRL_NATIVE_RUNNER_PYTHON": "/runner/python", + "HARBORRL_NATIVE_RUNNER_WORKERS": '["worker-a", "worker-b"]', + "HARBORRL_NATIVE_TOKENIZER_DIGEST": "0" * 64, + "HARBORRL_NATIVE_TEMPLATE_DIGEST": "1" * 64, + "ROLLOUT_MAX_RESPONSE_LEN": "128", "ROLLOUT_MAX_CONTEXT_LEN": "1024", } for key, value in environment.items(): monkeypatch.setenv(key, value) - - calls = {} - - class FakeGateway: - def __init__(self, registry, backend, *, model, max_output_tokens): - calls["gateway"] = (registry, backend, model, max_output_tokens) - calls["gateway_object"] = self - - class FakeServer: - def serve_forever(self): - pass - - def shutdown(self): - pass - - def server_close(self): - pass - - class FakeTraceRegistry: - def __init__(self, path): - calls["registry_path"] = path - - class FakeBackend: - def __init__(self, *args, **kwargs): - calls["backend_args"] = (args, kwargs) - - def weight_version(self): - return "7" - - def fake_make_server(gateway, host, port): - calls["server"] = (gateway, host, port) - return FakeServer() - + args = SimpleNamespace(rollout_num_gpus=8, rollout_num_gpus_per_engine=4, + sglang_server_concurrency=2, hf_checkpoint="/model", + sglang_router_ip="127.0.0.1", sglang_router_port=30000, + rollout_max_context_len=1024, rollout_max_response_len=128, + n_samples_per_prompt=4) + config = slime_rollout.runtime_config_from_args(args) + assert isinstance(config, RolloutRuntimeConfig) + assert config.endpoint == "http://127.0.0.1:30000" + assert config.concurrency_limit == 4 # 2 concurrency x 2 engines + assert config.group_size == 4 + assert config.workers == ("worker-a", "worker-b") + assert config.max_output_tokens == 128 + + # The tokenizer loader keeps the single Slime dependency at the boundary. slime = ModuleType("slime") utils = ModuleType("slime.utils") processing = ModuleType("slime.utils.processing_utils") + tokenizer = SimpleNamespace() processing.load_tokenizer = lambda *args, **kwargs: tokenizer slime.utils = utils utils.processing_utils = processing monkeypatch.setitem(sys.modules, "slime", slime) monkeypatch.setitem(sys.modules, "slime.utils", utils) monkeypatch.setitem(sys.modules, "slime.utils.processing_utils", processing) - monkeypatch.setattr(gateway_server, "Gateway", FakeGateway) - monkeypatch.setattr(gateway_server, "make_server", fake_make_server) - monkeypatch.setattr(sglang_backend, "SGLangBackend", FakeBackend) - monkeypatch.setattr(trace, "TraceRegistry", FakeTraceRegistry) - monkeypatch.setattr(native_generate, "_RUNTIME", None) - - args = SimpleNamespace(rollout_num_gpus=4, rollout_num_gpus_per_engine=4, - sglang_server_concurrency=1, hf_checkpoint="/model", - sglang_router_ip="127.0.0.1", sglang_router_port=30000, - rollout_max_context_len=1024, rollout_max_response_len=128) - runtime = asyncio.run(native_generate.runtime(args)) - try: - registry = calls["gateway"][0] - backend = calls["gateway"][1] - assert calls["gateway"] == (registry, backend, "policy", 128) - assert calls["server"][0] is calls["gateway_object"] - assert calls["server"][1:] == ("127.0.0.1", 49183) - assert runtime.backend.weight_version() == "7" - finally: - runtime.close() - native_generate._RUNTIME = None + assert slime_rollout.slime_tokenizer_loader(args)() is tokenizer def test_native_policy_identity_uses_synchronized_pool_version(tmp_path, monkeypatch): - from harborrl.rollout import native_generate + from harborrl.rollout import runtime as rollout_runtime class Backend: endpoint = "http://router:30000" @@ -314,26 +307,37 @@ def publish_version(self, states, expected): monkeypatch.setenv("RUN_DIR", str(tmp_path)) registry = Registry() - runtime = SimpleNamespace(run_root=tmp_path, backend=Backend(), registry=registry) - assert native_generate.current_policy_version(runtime) == "2" + runtime = SimpleNamespace(run_root=tmp_path, backend=Backend(), registry=registry, + config=SimpleNamespace(drain_timeout=30)) + assert rollout_runtime.current_policy_version(runtime) == "2" payload = json.loads((tmp_path / "policy-pool.json").read_text()) assert payload["weight_version"] == "2" assert registry.published[1] == "2" + assert registry.timeout == 30 def test_native_slots_spread_workers_and_shift_retries(tmp_path, monkeypatch): - from harborrl.rollout import native_generate + from harborrl.rollout import runtime as rollout_runtime entry = {"id": "task", "revision": "1", "path": "/task", "task_digest": "d" * 64, "reward_profile": {"key": "reward", "scale": 1.0, "offset": 0.0, "raw_range": [0.0, 1.0]}} profile = tmp_path / "profile.json" profile.write_text(json.dumps({"model": "policy"})) - monkeypatch.setenv("RUN_ID", "run") - monkeypatch.setenv("HARBORRL_NATIVE_PROFILE", str(profile)) - monkeypatch.setenv("HARBORRL_NATIVE_RUNNER_WORKERS", json.dumps(["worker-a", "worker-b"])) - monkeypatch.setenv("HARBORRL_NATIVE_MAX_ATTEMPTS", "2") - monkeypatch.setenv("HARBORRL_NATIVE_GATEWAY_URL", "http://gateway:32123") - monkeypatch.setenv("HARBORRL_NATIVE_RUNNER_PYTHON", "/python") + environment = { + "RUN_ID": "run", "RUN_DIR": str(tmp_path), "HARBORRL_NATIVE_PROFILE": str(profile), + "HARBORRL_NATIVE_RUNNER_WORKERS": json.dumps(["worker-a", "worker-b"]), + "HARBORRL_NATIVE_MAX_ATTEMPTS": "2", + "HARBORRL_NATIVE_GATEWAY_URL": "http://gateway:32123", + "HARBORRL_NATIVE_GATEWAY_PORT": "32123", + "HARBORRL_NATIVE_RUNNER_PYTHON": "/python", + "HARBORRL_NATIVE_CATALOG": str(tmp_path / "catalog.json"), + "HARBORRL_NATIVE_TOKENIZER_DIGEST": "0" * 64, + "HARBORRL_NATIVE_TEMPLATE_DIGEST": "1" * 64, + } + for key, value in environment.items(): + monkeypatch.setenv(key, value) + config = rollout_runtime.RolloutRuntimeConfig.from_environment( + environment, endpoint="http://router:30000", concurrency_limit=8, group_size=4) class FakeSlots: max_attempts = 2 @@ -360,19 +364,15 @@ async def fake_run_attempt(identity, *, runner_host, **kwargs): raise RuntimeError("simulated first-attempt failure") return {"identity": identity.__dict__} - monkeypatch.setattr(native_generate, "GroupSlots", fake_group_slots) - monkeypatch.setattr(native_generate, "run_attempt", fake_run_attempt) + monkeypatch.setattr(rollout_runtime, "GroupSlots", fake_group_slots) + monkeypatch.setattr(rollout_runtime, "run_attempt", fake_run_attempt) - args = SimpleNamespace(n_samples_per_prompt=4, rollout_max_response_len=64) runtime_ = SimpleNamespace(rollout_root=tmp_path, semaphore=asyncio.Semaphore(8), registry=SimpleNamespace(), profile={"model": "policy"}) - sample = SimpleNamespace(metadata={"native_task": entry}, group_index=0) async def gather_slots(): return await asyncio.gather(*[ - native_generate._run_slot( - args, runtime_, sample, slot, 0, entry, "sampling", "1" - ) + rollout_runtime.run_slot(config, runtime_, 0, slot, 0, entry, "sampling", "1") for slot in range(4)]) results = asyncio.run(gather_slots()) @@ -413,13 +413,96 @@ def test_native_dispatch_passes_materialized_prompt_data(tmp_path, monkeypatch): monkeypatch.setenv("SLIME_DIR", "/opt/slime-v0.3.2") monkeypatch.setenv("MEGATRON_DIR", "/opt/megatron") monkeypatch.setenv("SGLANG_IMAGE", "sglang:v0.5.15.post1-cu129") + monkeypatch.setenv("LR", "99") + monkeypatch.setenv("N_SAMPLES", "99") + monkeypatch.setenv("ANTHROPIC_API_KEY", "ambient-provider-credential") + monkeypatch.setenv("HTTPS_PROXY", "http://user:credential@proxy.example:8080") monkeypatch.setattr(native_train, "doctor", lambda *_args, **_kwargs: []) assert native_train.dispatch(plan) == 0 launch_path = next((tmp_path / "runs" / "training").glob("*/launch.json")) launch = json.loads(launch_path.read_text()) assert launch["prompt_data"] == output.read_text() + effective_text = (launch_path.parent / "effective-config.json").read_text() + effective = json.loads(effective_text) + assert effective['environment']['LR'] == '0.0001' + assert effective['environment']['N_SAMPLES'] == '2' + assert effective['environment']['ROLLOUT_PROMPT_DATA'] == output.read_text() + assert effective['components']['algorithm'] == 'grpo' + assert effective['deployment_environment']['SLIME_DIR'] == '/opt/slime-v0.3.2' + assert 'ambient-provider-credential' not in effective_text + assert 'user:credential' not in effective_text assert path_output.read_text().split(os.pathsep, 1)[0] == str(Path(plan["command"][0]).parent) assert json.loads(workers_output.read_text()) == ["worker-a", "worker-b"] assert gateway_output.read_text() == "gateway" assert backend_output.read_text() == ( "/opt/slime-v0.3.2:/opt/megatron:sglang:v0.5.15.post1-cu129") + + +def test_all_schema_fields_have_declared_consumers(): + from harborrl.backends.slime_v032.config import field_usage + from harborrl.config.native import FIELDS + assert set(field_usage()) == { + f'{section}.{field}' for section, fields in FIELDS.items() for field in fields + } + + +def test_schema_validation_does_not_read_harness_files(tmp_path): + from harborrl.config.native import validate, validate_files + config_path, _ = native_config(tmp_path) + config = load_config(config_path) + Path(config['harness']['profile']).unlink() + assert validate(config, config_path) is config + with pytest.raises(FileNotFoundError): + validate_files(config) + + +@pytest.mark.parametrize('field, value, message', [ + ('training', None, 'training'), + ('training.backend_contract', [], 'backend_contract'), + ('gateway.advertised_url', [], 'gateway.advertised_url'), + ('sampling.max_tokens', 'many', 'sampling.max_tokens'), +]) +def test_invalid_config_types_are_cli_errors(tmp_path, capsys, field, value, message): + import yaml + + from harborrl.cli import main + config_path, _ = native_config(tmp_path) + config = yaml.safe_load(config_path.read_text()) + section, _, key = field.partition('.') + if key: + config[section][key] = value + else: + config[section] = value + config_path.write_text(yaml.safe_dump(config)) + with pytest.raises(SystemExit) as exc: + main(['train', str(config_path), '--dry-run']) + assert exc.value.code == 2 + assert message in capsys.readouterr().err + + +def test_invalid_override_yaml_is_cli_error(tmp_path, capsys): + from harborrl.cli import main + config_path, _ = native_config(tmp_path) + with pytest.raises(SystemExit) as exc: + main(['train', str(config_path), '--set', 'training.learning_rate=[', '--dry-run']) + assert exc.value.code == 2 + assert 'training.learning_rate: invalid YAML override' in capsys.readouterr().err + + +def test_harness_file_override_is_checked_after_override(tmp_path): + config_path, _ = native_config(tmp_path) + config = load_config(config_path) + old_profile = Path(config['harness']['profile']) + new_profile = tmp_path / 'replacement.json' + old_profile.rename(new_profile) + config = load_config(config_path, [f'harness.profile={new_profile}']) + assert config['harness']['profile'] == str(new_profile) + + +def test_catalog_rejects_nonmapping_entry(tmp_path): + from harborrl.tasks.catalog import catalog + config_path, _ = native_config(tmp_path) + config = load_config(config_path) + Path(config['tasks']['catalog']).write_text('[null]') + with pytest.raises(ValueError, match='catalog identity'): + catalog(config) diff --git a/tests/harborrl/test_options.py b/tests/harborrl/test_options.py new file mode 100644 index 000000000..aa3739c4b --- /dev/null +++ b/tests/harborrl/test_options.py @@ -0,0 +1,308 @@ +"""Offline wizard contracts, including the existing training CLI boundary.""" +import json +import subprocess +import sys +from copy import deepcopy +from pathlib import Path + +import pytest +import yaml +from test_native_training import native_config + +from harborrl.options.catalog import TRAINING_FIELDS, build_catalog, dataset_metadata +from harborrl.options.cli import main +from harborrl.options.presets import PRESETS, worker_defaults +from harborrl.options.validation import validate_paths +from harborrl.options.wizard import build_config_bundle, publish_bundle + + +@pytest.fixture +def answers(tmp_path): + _path, task = native_config(tmp_path) + from harborrl.data.harbor.native_inspector import inspect_native + catalog = tmp_path / "catalog.json" + entries = json.loads(catalog.read_text()) + entries[0]["task_digest"] = inspect_native(task)["task_digest"] + catalog.write_text(json.dumps(entries)) + model = tmp_path / "model" + model.mkdir() + return {"answers_version": 1, "preset": "embedded-local", "training": { + "tasks": {"catalog": str(catalog)}, "harness": {"profile": str(tmp_path / "claude.json")}, + "harbor": {"python": sys.executable}, + "model": {"checkpoint": str(model), "reference": str(model)}, + "gateway": {"tokenizer_digest": "1" * 64, "template_digest": "2" * 64}, + }} + + +def service_answers(answers, preset): + answers = deepcopy(answers) + answers["preset"] = preset + answers["deployment"] = { + "manager": {"control_url": "http://controller.example:49200", "gateway_url": "http://controller.example:49184"}, + "gpu_target": {"ssh": {"host": "gpu", "user": "trainer", "port": 22}, + "source_dir": "/remote/source", "train_python": "/remote/python"}, + } + if preset == "service-ssh": + worker = worker_defaults() + worker["ssh"]["host"] = "runner" + answers["deployment"]["rollout"] = {"workers": [worker]} + del answers["training"]["harbor"]["python"] + else: + answers["sandbox_config"] = "/site/sandbox.yaml" + return answers + + +def test_catalog_contract(monkeypatch): + def forbidden(*args, **kwargs): + raise AssertionError("offline operation attempted subprocess") + monkeypatch.setattr(subprocess, "run", forbidden) + monkeypatch.setattr(subprocess, "Popen", forbidden) + from harborrl.config.native import FIELDS + assert set(TRAINING_FIELDS) == {f"{s}.{f}" for s, fields in FIELDS.items() for f in fields} + first = build_catalog() + assert json.dumps(first, sort_keys=True) == json.dumps(build_catalog(), sort_keys=True) + assert all(not row["available"] for row in first["datasets"] if row["status"] == "planned") + from harborrl.config import ROOT + assert dataset_metadata() == yaml.safe_load((ROOT / "configs/harbor_hub/manifests.yaml").read_text()) + + +@pytest.mark.parametrize("dimension", [None, "models", "algorithms", "tasks", "harnesses", "datasets", "deployments", "fields", "compat"]) +def test_catalog_cli(dimension, capsys): + assert main(["options", *([dimension] if dimension else []), "--json"]) == 0 + captured = capsys.readouterr() + result = json.loads(captured.out) + assert not captured.err + assert result["catalog_version"] == 1 + if dimension: + assert len(result) == 3 + + +@pytest.mark.parametrize("preset", [p["id"] for p in PRESETS]) +def test_presets_and_legacy_dry_run(preset, answers, tmp_path, capsys): + if preset != "embedded-local": + answers = service_answers(answers, preset) + original = deepcopy(answers) + output = tmp_path / "new site" + bundle = build_config_bundle(answers, output_directory=output) + assert answers == original + assert not output.exists() + publish_bundle(bundle, output) + result = validate_paths(output / "config.yaml", output / "deployment.yaml") + assert result["status"] == "valid_with_deferred_checks", result + if preset != "service-sandbox-local-runner": + from harborrl.cli import main as legacy_main + assert legacy_main(["train", str(output / "config.yaml"), "--deployment", str(output / "deployment.yaml"), "--dry-run"]) == 0 + assert json.loads(capsys.readouterr().out)["config"]["schema_version"] == 2 + + +def test_local_files_and_remote_ownership(answers, tmp_path): + for preset in ("embedded-local", "service-ssh"): + current = answers if preset == "embedded-local" else service_answers(answers, preset) + out = tmp_path / preset + publish_bundle(build_config_bundle(current, output_directory=out), out) + result = validate_paths(out / "config.yaml", out / "deployment.yaml", check_files=True) + assert result["status"] == "valid_with_deferred_checks", result + if preset == "service-ssh": + assert any(c["code"] == "remote_paths" for c in result["checks"]) + (tmp_path / "claude.json").unlink() + assert validate_paths(out / "config.yaml", out / "deployment.yaml")["status"] != "invalid" + assert validate_paths(out / "config.yaml", out / "deployment.yaml", check_files=True)["status"] == "invalid" + + +@pytest.mark.parametrize("mutation", ["unknown", "missing", "zero_digest", "gpu", "gateway", "provider"]) +def test_reject_bad_answers(mutation, answers, tmp_path): + answers = service_answers(answers, "service-ssh") + if mutation == "unknown": + answers["training"]["sampling"] = {"typo": 1} + elif mutation == "missing": + del answers["training"]["tasks"] + elif mutation == "zero_digest": + answers["training"]["gateway"]["tokenizer_digest"] = "0" * 64 + elif mutation == "gpu": + answers["deployment"]["gpu_target"]["expected_gpus"] = 0 + elif mutation == "gateway": + answers["training"]["gateway"]["advertised_url"] = "http://other.example:49184" + else: + answers["deployment"]["rollout"]["provider"] = "sandbox" + with pytest.raises(ValueError): + build_config_bundle(answers, output_directory=tmp_path / "output") + assert not (tmp_path / "output").exists() + + +def test_noninteractive_and_failure_json(answers, tmp_path, monkeypatch, capsys): + monkeypatch.setattr(sys.stdin, "isatty", lambda: False) + with pytest.raises(SystemExit) as exc: + main(["init", "--out-dir", str(tmp_path / "output")]) + assert exc.value.code == 2 + capsys.readouterr() + assert main(["validate", str(tmp_path / "missing"), "--json"]) == 1 + assert json.loads(capsys.readouterr().out)["status"] == "invalid" + file = tmp_path / "answers.yaml" + file.write_text(yaml.safe_dump(answers)) + assert main(["init", "--answers", str(file), "--out-dir", str(tmp_path / "output")]) == 0 + assert "harborrl train" in capsys.readouterr().out + assert main(["init", "--answers", str(file), "--out-dir", str(tmp_path / "output")]) == 1 + + +def test_publication_failure_lock_and_traversal(answers, tmp_path, monkeypatch): + output = tmp_path / "output" + bundle = build_config_bundle(answers, output_directory=output) + lock = tmp_path / ".output.init.lock" + lock.write_text("other process") + with pytest.raises(FileExistsError): + publish_bundle(bundle, output) + assert lock.read_text() == "other process" + lock.unlink() + bad = deepcopy(bundle) + bad["files"]["../escape"] = "bad" + with pytest.raises(ValueError): + publish_bundle(bad, output) + def fail(*args, **kwargs): + raise OSError("injected rename failure") + with monkeypatch.context() as context: + context.setattr(Path, "rename", fail) + with pytest.raises(OSError, match="injected"): + publish_bundle(bundle, output) + assert not output.exists() and not list(tmp_path.glob(".output.init*")) + publish_bundle(bundle, output) + before = (output / "config.yaml").read_bytes() + with pytest.raises(FileExistsError): + publish_bundle(bundle, output) + assert (output / "config.yaml").read_bytes() == before + + +@pytest.mark.parametrize("confirmation", ["yes", "no"]) +def test_interactive(answers, tmp_path, monkeypatch, confirmation): + t = answers["training"] + responses = iter(["1", sys.executable, sys.executable, t["tasks"]["catalog"], t["harness"]["profile"], + t["model"]["checkpoint"], t["model"]["reference"], "", "1" * 64, "2" * 64, + "", "", "1", "", "", "", "", confirmation]) + monkeypatch.setattr(sys.stdin, "isatty", lambda: True) + monkeypatch.setattr("builtins.input", lambda _: next(responses)) + output = tmp_path / "interactive" + assert main(["init", "--out-dir", str(output)]) == 0 + assert output.exists() == (confirmation == "yes") + + +def test_probe_failure_and_alias_parsing(tmp_path, monkeypatch): + from harborrl.options.probe import probe_local, ssh_aliases + config = tmp_path / "ssh-config" + config.write_text("Host train runner *.example !excluded\n HostName host\nMatch exec unsafe\n User user\n") + assert ssh_aliases(config) == ["runner", "train"] + def timeout(*args, **kwargs): + assert kwargs["timeout"] == 3 + raise subprocess.TimeoutExpired(args[0], 3) + monkeypatch.setattr(subprocess, "run", timeout) + result = probe_local(tmp_path / "output") + assert result["gpu"]["status"] == result["python"]["status"] == "unknown" + assert not (tmp_path / "output").exists() + + +def test_sandbox_files_and_remote_runner(answers, tmp_path, capsys): + from harborrl.cli import main as legacy_main + from harborrl.data.harbor.native_inspector import inspect_native + task = tmp_path / "task" + (task / "task.toml").write_text('version = "1.0"\n[environment]\ndocker_image = "task:locked"\n') + catalog = tmp_path / "catalog.json" + entries = json.loads(catalog.read_text()) + entries[0]["task_digest"] = inspect_native(task)["task_digest"] + catalog.write_text(json.dumps(entries)) + sandbox = tmp_path / "sandbox.yaml" + sandbox.write_text(yaml.safe_dump({"domain": "https://sandbox.example", "credential_file": "/missing/secret", + "bindings": [{"image": "task:locked", "max_live_instances": 2}]})) + answers = service_answers(answers, "service-sandbox-local-runner") + answers["sandbox_config"] = str(sandbox) + for remote in (False, True): + if remote: + worker = worker_defaults() + worker["ssh"]["host"] = "runner" + worker["docker"] = False + answers["deployment"]["rollout"] = {"workers": [worker]} + answers["training"]["harbor"] = {"runner_transport": "ssh"} + output = tmp_path / f"sandbox-{remote}" + publish_bundle(build_config_bundle(answers, output_directory=output), output) + report = validate_paths(output / "config.yaml", output / "deployment.yaml", check_files=True) + assert report["status"] == "valid_with_deferred_checks", report + assert legacy_main(["train", str(output / "config.yaml"), "--deployment", str(output / "deployment.yaml"), "--dry-run"]) == 0 + capsys.readouterr() + sandbox.write_text(sandbox.read_text().replace("task:locked", "other:locked")) + assert validate_paths(output / "config.yaml", output / "deployment.yaml", check_files=True)["status"] == "invalid" + + +def test_offline_import_boundary(answers, tmp_path): + from harborrl.config import ROOT + output = tmp_path / "offline" + publish_bundle(build_config_bundle(answers, output_directory=output), output) + script = ''' +import builtins, socket, subprocess, sys +sys.path.insert(0, sys.argv[1]) +original = builtins.__import__ +def guarded(name, *args, **kwargs): + assert name.split('.')[0] not in {'torch', 'transformers', 'sglang', 'harbor', 'opensandbox', 'httpx'} + return original(name, *args, **kwargs) +def forbidden(*args, **kwargs): + raise AssertionError('offline command attempted an external operation') +builtins.__import__ = guarded +socket.socket = forbidden +subprocess.run = subprocess.Popen = forbidden +from harborrl.cli import main +assert main(['options', '--json']) == 0 +assert main(['validate', sys.argv[2], '--deployment', sys.argv[3], '--check-files', '--json']) == 0 +''' + result = subprocess.run([sys.executable, "-I", "-c", script, str(ROOT), str(output / "config.yaml"), + str(output / "deployment.yaml")], capture_output=True, text=True, timeout=20, check=False) + assert result.returncode == 0, result.stderr + + +def test_concurrent_publication(answers, tmp_path): + from concurrent.futures import ThreadPoolExecutor + from threading import Barrier + output = tmp_path / "concurrent" + bundle = build_config_bundle(answers, output_directory=output) + barrier = Barrier(2) + def attempt(): + barrier.wait(timeout=10) + try: + publish_bundle(bundle, output) + except FileExistsError: + return False + return True + with ThreadPoolExecutor(max_workers=2) as pool: + assert sorted(pool.map(lambda _: attempt(), range(2))) == [False, True] + assert {p.name for p in output.iterdir()} == set(bundle["files"]) + + +def test_relative_paths_and_invalid_yaml(answers, tmp_path, capsys): + answers["training"]["tasks"]["catalog"] = "../catalog.json" + output = tmp_path / "relative" + publish_bundle(build_config_bundle(answers, output_directory=output), output) + config = yaml.safe_load((output / "config.yaml").read_text()) + assert config["tasks"]["catalog"] == str(tmp_path / "catalog.json") + bad = tmp_path / "bad.yaml" + bad.write_text("[invalid") + assert main(["validate", str(bad), "--json"]) == 1 + assert json.loads(capsys.readouterr().out)["status"] == "invalid" + + +def test_embedded_explicit_conflict(answers, tmp_path): + answers["training"]["gateway"]["advertised_url"] = "http://localhost:49184" + answers["deployment"] = {"manager": {"gateway_url": "http://127.0.0.1:49184"}} + with pytest.raises(ValueError, match="conflicts"): + build_config_bundle(answers, output_directory=tmp_path / "conflict") + + +def test_options_text_is_concise(capsys): + assert main(["options"]) == 0 + output = capsys.readouterr().out + assert len(output.splitlines()) < 50 + assert "locally_available=False" in output + + +def test_public_answer_templates(tmp_path): + from harborrl.config import ROOT + for file in sorted((ROOT / "examples/options").glob("*.yaml")): + answers = yaml.safe_load(file.read_text()) + with pytest.raises(ValueError, match="SHA-256"): + build_config_bundle(answers, output_directory=tmp_path / file.stem) + answers["training"]["gateway"].update(tokenizer_digest="1" * 64, template_digest="2" * 64) + assert build_config_bundle(answers, output_directory=tmp_path / file.stem)["validation"]["status"] != "invalid" diff --git a/tests/harborrl/test_rollout_runtime.py b/tests/harborrl/test_rollout_runtime.py new file mode 100644 index 000000000..8f33323c5 --- /dev/null +++ b/tests/harborrl/test_rollout_runtime.py @@ -0,0 +1,321 @@ +"""Extracted rollout core (P2): config mapping, slot scheduling, boundaries.""" +from __future__ import annotations + +import asyncio +import json +from pathlib import Path +from types import SimpleNamespace + +import pytest + + +def test_runtime_config_maps_environment(tmp_path, monkeypatch): + from harborrl.rollout.runtime import RolloutRuntimeConfig + + environment = { + "RUN_ID": "run-1", "RUN_DIR": str(tmp_path), + "HARBORRL_NATIVE_CATALOG": str(tmp_path / "catalog.json"), + "HARBORRL_NATIVE_PROFILE": str(tmp_path / "profile.json"), + "HARBORRL_NATIVE_GATEWAY_HOST": "127.0.0.1", + "HARBORRL_NATIVE_GATEWAY_PORT": "49183", + "HARBORRL_NATIVE_GATEWAY_URL": "http://gateway:49183", + "HARBORRL_NATIVE_RUNNER_PYTHON": "/runner/python", + "HARBORRL_NATIVE_RUNNER_WORKERS": json.dumps(["worker-a", "worker-b"]), + "HARBORRL_NATIVE_TASK_WORKER_OVERRIDES": json.dumps({"task": "worker-b"}), + "HARBORRL_NATIVE_MAX_ATTEMPTS": "3", + "ROLLOUT_MAX_RESPONSE_LEN": "128", + "ROLLOUT_MAX_CONTEXT_LEN": "1024", + "HARBORRL_NATIVE_TOKENIZER_DIGEST": "0" * 64, + "HARBORRL_NATIVE_TEMPLATE_DIGEST": "1" * 64, + "HARBORRL_NATIVE_AUDITED_LOGPROBS": "1", + "HARBORRL_NATIVE_DRAIN_TIMEOUT": "5", + "MODEL_TAG": "qwen3-8B", + } + for key, value in environment.items(): + monkeypatch.setenv(key, value) + config = RolloutRuntimeConfig.from_environment( + environment, endpoint="http://router:30000/", concurrency_limit=4, group_size=2) + assert config.workers == ("worker-a", "worker-b") + assert config.task_worker_overrides == {"task": "worker-b"} + assert config.max_attempts == 3 and config.max_output_tokens == 128 + assert config.endpoint == "http://router:30000" + assert config.environment()["HARBORRL_NATIVE_RUNNER_WORKERS"] == \ + json.dumps(["worker-a", "worker-b"]) + with pytest.raises(ValueError, match="group size"): + RolloutRuntimeConfig.from_environment(environment, endpoint="x", concurrency_limit=1, + group_size=1) + stripped = dict(environment) + stripped.pop("HARBORRL_NATIVE_RUNNER_WORKERS") + with pytest.raises(ValueError, match="RUNNER_WORKERS"): + RolloutRuntimeConfig.from_environment(stripped, endpoint="x", + concurrency_limit=1, group_size=2) + + +def test_runtime_binds_gateway_with_registry_and_backend(tmp_path, monkeypatch): + from harborrl.gateway import server as gateway_server + from harborrl.gateway import sglang_backend, trace + from harborrl.rollout import runtime as rollout_runtime + + catalog = tmp_path / "catalog.json" + catalog.write_text("[]") + profile = tmp_path / "profile.json" + profile.write_text(json.dumps({"model": "policy"})) + tokenizer = SimpleNamespace( + apply_chat_template=lambda *args, **kwargs: [1, 2], + get_vocab=lambda: {"token": 1}, + ) + tokenizer_digest, template_digest = rollout_runtime.tokenizer_fingerprint(tokenizer) + environment = { + "RUN_DIR": str(tmp_path), "RUN_ID": "run-1", + "HARBORRL_NATIVE_CATALOG": str(catalog), + "HARBORRL_NATIVE_PROFILE": str(profile), + "HARBORRL_NATIVE_TOKENIZER_DIGEST": tokenizer_digest, + "HARBORRL_NATIVE_TEMPLATE_DIGEST": template_digest, + "HARBORRL_NATIVE_GATEWAY_HOST": "127.0.0.1", + "HARBORRL_NATIVE_GATEWAY_PORT": "49183", + "HARBORRL_NATIVE_GATEWAY_URL": "http://gateway:49183", + "HARBORRL_NATIVE_RUNNER_PYTHON": "/runner/python", + "HARBORRL_NATIVE_RUNNER_WORKERS": '["worker-a"]', + "ROLLOUT_MAX_RESPONSE_LEN": "128", + "ROLLOUT_MAX_CONTEXT_LEN": "1024", + } + for key, value in environment.items(): + monkeypatch.setenv(key, value) + + calls = {} + + class FakeGateway: + def __init__(self, registry, backend, *, model, max_output_tokens): + calls["gateway"] = (registry, backend, model, max_output_tokens) + calls["gateway_object"] = self + + class FakeServer: + def serve_forever(self): + pass + + def shutdown(self): + pass + + def server_close(self): + pass + + class FakeTraceRegistry: + def __init__(self, path): + calls["registry_path"] = path + + class FakeBackend: + def __init__(self, *args, **kwargs): + calls["backend_args"] = (args, kwargs) + + def weight_version(self): + return "7" + + def fake_make_server(gateway, host, port): + calls["server"] = (gateway, host, port) + return FakeServer() + + monkeypatch.setattr(gateway_server, "Gateway", FakeGateway) + monkeypatch.setattr(gateway_server, "make_server", fake_make_server) + monkeypatch.setattr(sglang_backend, "SGLangBackend", FakeBackend) + monkeypatch.setattr(trace, "TraceRegistry", FakeTraceRegistry) + + config = rollout_runtime.RolloutRuntimeConfig.from_environment( + environment, endpoint="http://127.0.0.1:30000", concurrency_limit=4, group_size=2) + runtime_ = rollout_runtime.RolloutRuntime(config, tokenizer) + try: + assert calls["gateway"][2:] == ("policy", 128) + assert calls["server"][0] is calls["gateway_object"] + assert calls["server"][1:] == ("127.0.0.1", 49183) + assert calls["backend_args"][0][0] == "http://127.0.0.1:30000" + assert runtime_.backend.weight_version() == "7" + finally: + runtime_.close() + + # A tokenizer that disagrees with the native lock fails closed. + other = SimpleNamespace(apply_chat_template=lambda *a, **k: [9], + get_vocab=lambda: {"other": 2}) + with pytest.raises(ValueError, match="digest"): + rollout_runtime.RolloutRuntime(config, other) + + +def test_policy_identity_uses_synchronized_pool_version(tmp_path, monkeypatch): + from harborrl.rollout import runtime as rollout_runtime + + class Backend: + endpoint = "http://router:30000" + + def pool_states(self): + return [{"endpoint": self.endpoint, "healthy": True, "weight_version": "2"}] + + class Registry: + def __init__(self): + self.published = None + + def drain(self, timeout): + self.timeout = timeout + + def publish_version(self, states, expected): + self.published = (states, expected) + + monkeypatch.setenv("RUN_DIR", str(tmp_path)) + registry = Registry() + runtime_ = SimpleNamespace(run_root=tmp_path, backend=Backend(), registry=registry, + config=SimpleNamespace(drain_timeout=30)) + assert rollout_runtime.current_policy_version(runtime_) == "2" + payload = json.loads((tmp_path / "policy-pool.json").read_text()) + assert payload["weight_version"] == "2" + assert registry.published[1] == "2" + assert registry.timeout == 30 + + +def test_group_shares_slot_state_and_shifts_retry_workers(tmp_path, monkeypatch): + from harborrl.rollout import runtime as rollout_runtime + + entry = {"id": "task", "revision": "1", "path": "/task", "task_digest": "d" * 64, + "reward_profile": {"key": "reward", "scale": 1.0, "offset": 0.0, + "raw_range": [0.0, 1.0]}} + profile = tmp_path / "profile.json" + profile.write_text(json.dumps({"model": "policy"})) + environment = { + "RUN_ID": "run", "RUN_DIR": str(tmp_path), "HARBORRL_NATIVE_PROFILE": str(profile), + "HARBORRL_NATIVE_RUNNER_WORKERS": json.dumps(["worker-a", "worker-b"]), + "HARBORRL_NATIVE_MAX_ATTEMPTS": "2", + "HARBORRL_NATIVE_GATEWAY_URL": "http://gateway:32123", + "HARBORRL_NATIVE_GATEWAY_PORT": "32123", + "HARBORRL_NATIVE_RUNNER_PYTHON": "/python", + "HARBORRL_NATIVE_CATALOG": str(tmp_path / "catalog.json"), + "HARBORRL_NATIVE_TOKENIZER_DIGEST": "0" * 64, + "HARBORRL_NATIVE_TEMPLATE_DIGEST": "1" * 64, + } + for key, value in environment.items(): + monkeypatch.setenv(key, value) + config = rollout_runtime.RolloutRuntimeConfig.from_environment( + environment, endpoint="http://router:30000", concurrency_limit=8, group_size=4) + + class FakeSlots: + max_attempts = 2 + + def __init__(self): + self.started = {} + + def start(self, slot): + self.started[slot] = self.started.get(slot, 0) + 1 + return SimpleNamespace(attempt_id=f"{slot}-{self.started[slot]}", + group_id="group-0", slot_id=slot, + attempt_no=self.started[slot]) + + def finish(self, slot, ir=None, error=None): + assert (ir is None) != (error is None) + + created_groups = [] + + def fake_group_slots(identities, *, max_attempts=2): + created_groups.append(identities) + return FakeSlots() + + calls = [] + + async def fake_run_attempt(identity, *, runner_host, **kwargs): + calls.append((identity.slot_id, runner_host)) + if identity.slot_id == "1" and identity.attempt_no == 1: + raise RuntimeError("simulated first-attempt failure") + return {"identity": identity.__dict__} + + monkeypatch.setattr(rollout_runtime, "GroupSlots", fake_group_slots) + monkeypatch.setattr(rollout_runtime, "run_attempt", fake_run_attempt) + + runtime_ = SimpleNamespace(rollout_root=tmp_path, semaphore=asyncio.Semaphore(8), + registry=SimpleNamespace(), profile={"model": "policy"}) + + # The run owns its loaded profile; group setup must not reread the source file. + profile.unlink() + results = asyncio.run(rollout_runtime.run_group( + config, runtime_, entry=entry, group_index=0, rollout_id=0, + sampling_digest="sampling", policy_version="1")) + assert len(results) == 4 + assert len(created_groups) == 1 + assert len(created_groups[0]) == 4 + assert {item.harness_digest for item in created_groups[0]} == { + rollout_runtime.digest(runtime_.profile)} + by_slot = {} + for slot, host in calls: + by_slot.setdefault(slot, []).append(host) + assert by_slot == {"0": ["worker-a"], "1": ["worker-b", "worker-a"], + "2": ["worker-a"], "3": ["worker-b"]} + + +def test_task_worker_override_wins(tmp_path, monkeypatch): + from harborrl.rollout import runtime as rollout_runtime + + entry = {"id": "task", "revision": "1", "path": "/task", "task_digest": "d" * 64, + "reward_profile": {"key": "reward"}} + profile = tmp_path / "profile.json" + profile.write_text(json.dumps({"model": "policy"})) + environment = { + "RUN_ID": "run", "RUN_DIR": str(tmp_path), "HARBORRL_NATIVE_PROFILE": str(profile), + "HARBORRL_NATIVE_RUNNER_WORKERS": json.dumps(["worker-a", "worker-b"]), + "HARBORRL_NATIVE_TASK_WORKER_OVERRIDES": json.dumps({"task": "worker-b"}), + "HARBORRL_NATIVE_GATEWAY_URL": "http://gateway:32123", + "HARBORRL_NATIVE_GATEWAY_PORT": "32123", "HARBORRL_NATIVE_RUNNER_PYTHON": "/python", + "HARBORRL_NATIVE_CATALOG": str(tmp_path / "catalog.json"), + "HARBORRL_NATIVE_TOKENIZER_DIGEST": "0" * 64, + "HARBORRL_NATIVE_TEMPLATE_DIGEST": "1" * 64, + } + for key, value in environment.items(): + monkeypatch.setenv(key, value) + config = rollout_runtime.RolloutRuntimeConfig.from_environment( + environment, endpoint="http://router:30000", concurrency_limit=2, group_size=2) + + seen = [] + + class PermissiveSlots: + max_attempts = 2 + + def __init__(self, identities, *, max_attempts=2): + self.identities = {identity.slot_id: identity for identity in identities} + + def start(self, slot): + return self.identities[slot] + + def finish(self, slot, ir=None, error=None): + assert (ir is None) != (error is None) + + async def fake_run_attempt(identity, *, runner_host, **kwargs): + seen.append(runner_host) + return {"identity": identity.to_dict()} + + monkeypatch.setattr(rollout_runtime, "GroupSlots", PermissiveSlots) + monkeypatch.setattr(rollout_runtime, "run_attempt", fake_run_attempt) + runtime_ = SimpleNamespace(rollout_root=tmp_path, semaphore=asyncio.Semaphore(2), + registry=SimpleNamespace(), profile={"model": "policy"}) + irs = asyncio.run(rollout_runtime.run_group( + config, runtime_, entry=entry, group_index=0, rollout_id=0, + sampling_digest="sampling", policy_version="1")) + assert len(irs) == 2 + assert seen == ["worker-b", "worker-b"] + + +def test_rollout_core_does_not_import_training_frameworks(): + import re + + import harborrl.rollout + + root = Path(harborrl.rollout.__file__).parent + offenders = [] + for path in sorted(root.rglob("*.py")): + for number, line in enumerate(path.read_text().splitlines(), 1): + if re.match(r"\s*(from|import)\s+(slime|ray)(\.|\s|$)", line): + offenders.append(f"{path.relative_to(root)}:{number}: {line.strip()}") + assert not offenders, "rollout core must stay framework-free: " + "; ".join(offenders) + + +def test_native_generate_shim_reexports_the_core(): + from harborrl import rollout as package + from harborrl.rollout import native_generate, runtime + + for name in ("NativeRolloutRuntime", "RolloutRuntime", "RolloutRuntimeConfig", + "close_runtime", "current_policy_version", "publish_native_version", + "run_attempt", "run_group", "run_slot", "runtime", "task_entry", + "tokenizer_fingerprint", "GroupSlots"): + assert getattr(native_generate, name) is getattr(runtime, name), name + assert package.native_generate.__doc__ is not None diff --git a/tests/harborrl/test_rollout_service.py b/tests/harborrl/test_rollout_service.py new file mode 100644 index 000000000..f12994feb --- /dev/null +++ b/tests/harborrl/test_rollout_service.py @@ -0,0 +1,415 @@ +"""RolloutService + SlimeAdapter contracts (P3) over real local HTTP.""" +from __future__ import annotations + +import asyncio +import json +import threading +import time +from pathlib import Path +from types import SimpleNamespace + +import pytest +from test_native_contracts import SAMPLING, receipt + +from harborrl.backends.slime_v032.rollout_service_client import ( + RolloutServiceClient, + ServiceRolloutError, + load_group_result, + service_group_request, +) +from harborrl.rollout.service import ( + RolloutService, + make_control_server, + manifest_path_from, +) +from harborrl.trajectories.native import Identity, RewardProfile, digest, publish + +TASK = {"id": "task", "revision": "1", "path": "/task", "task_digest": "d" * 64, + "reward_profile": {"key": "reward", "scale": 1.0, "offset": 0.0, + "raw_range": [0.0, 1.0]}} + + +def service_identity(slot, *, run_id="run-1", group_id="group-0", policy_version="1"): + return Identity(run_id=run_id, batch_id="rollout-0", group_id=group_id, + slot_id=slot, attempt_id=f"attempt-{policy_version}-{slot}", + trajectory_id=f"trajectory-{policy_version}-{slot}", + task_digest=TASK["task_digest"], policy_version=policy_version, + harness_digest="claude", reward_digest=RewardProfile().digest, + sampling_digest=digest(SAMPLING)) + + +def service_ir(tmp_path, slot, reward=1, **identity_overrides): + from harborrl.trajectories.native import assemble + + ident = service_identity(slot, **identity_overrides) + evaluation = receipt(tmp_path / slot, ident, reward) + trial = evaluation["harbor_trial_id"] + turn = {"identity": ident.to_dict(), "harbor_trial_id": trial, + "request_id": f"r-{slot}", "response_id": f"m-{slot}", "engine_id": "engine", + "input_ids": [1, 2], "output_ids": [3, 4], "logprobs": [-0.2, -0.3], + "logprob_semantics": "raw_model", "policy_version": ident.policy_version, + "tokenizer_digest": "tok", "template_digest": "template", + "delivery": "consumed_confirmed", "consumption_ref": "session#digest", + "role": "policy", "sampling": SAMPLING, "client_request": {"model": "policy"}, + "serving_input": {"input_ids": [1, 2]}, + "response": {"content": [{"type": "text", "text": "done"}]}, + "finish_reason": "end_turn", "evidence_kind": "synthetic"} + for key in ("client_request", "serving_input", "response"): + turn[key + "_digest"] = digest(turn[key]) + seal = {"identity": ident.to_dict(), "harbor_trial_id": trial, + "pending_requests": 0, "status": "sealed", + "request_ids": [turn["request_id"]], "turns_digest": digest([turn]), + "errors": []} + return assemble(ident, trial, [turn], seal, evaluation) + + +class FakeRegistry: + def __init__(self): + self.version = None + self.accepting = False + + def drain(self, timeout): + self.accepting = False + + def publish_version(self, states, expected): + self.version = str(expected) + self.accepting = True + + +class FakeRuntime: + def __init__(self, root): + self.registry = FakeRegistry() + self.rollout_root = Path(root) + + def manifest_path(self, identity): + return manifest_path_from(self.rollout_root, identity) + + def close(self): + pass + + +def write_group_irs(tmp_path, request, rewards=(1, 0)): + runtime = FakeRuntime(tmp_path) + irs = [] + for descriptor, reward in zip(request["slots"], rewards): + ir = service_ir(tmp_path, descriptor["slot_id"], reward, + policy_version=request["policy_version"]) + publish(runtime.manifest_path(ir["identity"]), ir) + irs.append(ir) + return irs + + +def group_request(group_index=0, policy_version="1", slots=2, task=TASK): + return { + "group_id": f"group-{group_index}", "rollout_id": 0, + "group_index": group_index, "policy_version": policy_version, + "task": task, "sampling": dict(SAMPLING), + "slots": [{"slot_id": str(slot)} for slot in range(slots)], + } + + +class ServiceHarness: + def __init__(self, tmp_path, executor, **kwargs): + self.tmp_path = tmp_path + self.runtime = FakeRuntime(tmp_path) + self.service = RolloutService(run_root=tmp_path, executor=executor, allow_synthetic=True, **kwargs) + self.control = make_control_server(self.service, host="127.0.0.1", port=0) + self.thread = threading.Thread(target=self.control.serve_forever, daemon=True) + self.thread.start() + self.url = f"http://127.0.0.1:{self.control.server_address[1]}" + self.run_id = "run-1" + registration = self.service.register_run( + self.run_id, {"run_id": self.run_id, "group_size": 2}) + self.token = registration["run_token"] + self.service.attach_runtime(self.run_id, SimpleNamespace(drain_timeout=1), + self.runtime) + + def client(self): + return RolloutServiceClient(self.url, self.run_id, self.token, timeout=10, + poll_interval=0.05) + + def close(self): + self.control.shutdown() + self.control.server_close() + self.thread.join(5) + self.service.close() + + +@pytest.fixture() +def harness(tmp_path): + calls = [] + + async def executor(config, runtime_, request): + calls.append(json.loads(json.dumps(request))) + return write_group_irs(tmp_path, request) + + instance = ServiceHarness(tmp_path, executor, group_wait_timeout=5) + instance.executor_calls = calls + try: + yield instance + finally: + instance.close() + + +def activated_states(version="1"): + return [{"endpoint": f"http://engine-{index}", "healthy": True, + "weight_version": version} for index in range(2)] + + +def test_register_auth_and_idempotency(harness): + client = harness.client() + assert client.health()["ok"] + # Wrong tokens are rejected on every run-scoped endpoint. + bad = RolloutServiceClient(harness.url, harness.run_id, "wrong-token", timeout=10) + with pytest.raises(ServiceRolloutError) as exc: + bad.group_status("group-0") + assert exc.value.status == 401 + with pytest.raises(ServiceRolloutError) as exc: + bad.submit_group(group_request()) + assert exc.value.status == 401 + + +def test_policy_gate_and_activation(harness, tmp_path): + client = harness.client() + with pytest.raises(ServiceRolloutError) as exc: + client.submit_group(group_request()) + assert exc.value.status == 409 and exc.value.code == "stale_policy" + assert not (tmp_path / "policy-pool.json").exists() + + client.activate_policy(activated_states(), "1") + assert harness.runtime.registry.version == "1" + assert harness.runtime.registry.accepting + pool = json.loads((tmp_path / "policy-pool.json").read_text()) + assert pool["weight_version"] == "1" + + # A partially unhealthy pool is never admitted. + from harborrl.rollout.service import ServiceError + + broken = activated_states() + broken[1]["healthy"] = False + with pytest.raises(ServiceError, match="pool"): + harness.service.activate_policy(harness.run_id, broken, "2") + + +def test_submit_is_idempotent_and_conflicts_rejected(harness): + client = harness.client() + client.activate_policy(activated_states(), "1") + request = group_request() + first = client.submit_group(request) + assert first["status"] == "complete" + assert len(first["result"]["slots"]) == 2 + assert len(harness.executor_calls) == 1 + + again = client.submit_group(json.loads(json.dumps(request))) + assert again["status"] == "complete" + assert again["result"]["request_digest"] == first["result"]["request_digest"] + assert len(harness.executor_calls) == 1 # no second execution + + conflicting = group_request() + conflicting["sampling"]["temperature"] = 0.5 + with pytest.raises(ServiceRolloutError) as exc: + client.submit_group(conflicting) + assert exc.value.status == 409 and exc.value.code == "conflict" + + other = client.submit_group(group_request(group_index=1)) + assert other["status"] == "complete" + assert len(harness.executor_calls) == 2 + + +def test_client_validates_result_and_rejects_tampering(harness, tmp_path): + client = harness.client() + client.activate_policy(activated_states(), "1") + request = group_request() + result = client.submit_group(request)["result"] + trajectories = load_group_result(harness.run_id, request, result, + allow_synthetic=True) + assert [ir["identity"]["slot_id"] for ir in trajectories] == ["0", "1"] + + slot = result["slots"][0] + manifest = Path(slot["manifest_path"]) + forged = json.loads(json.dumps(result)) + forged["slots"][0]["manifest_sha256"] = "0" * 64 + with pytest.raises(ServiceRolloutError, match="digest mismatch"): + load_group_result(harness.run_id, request, forged, allow_synthetic=True) + tampered = json.loads(json.dumps(result)) + tampered["slots"][1]["manifest_path"] = str(tmp_path / "missing.json") + with pytest.raises(ServiceRolloutError, match="cannot read slot manifest"): + load_group_result(harness.run_id, request, tampered, allow_synthetic=True) + assert manifest.is_file() # nothing above mutated shared evidence + + # A stale policy version inside a result is rejected. + fresh = write_group_irs(tmp_path, group_request(policy_version="2"), rewards=(1, 0)) + stale_result = json.loads(json.dumps(result)) + stale_result["slots"][0]["manifest_path"] = str( + harness.runtime.manifest_path(fresh[0]["identity"])) + stale_result["slots"][0]["manifest_sha256"] = digest(fresh[0]) + with pytest.raises(ServiceRolloutError, match="identity differs"): + load_group_result(harness.run_id, request, stale_result, allow_synthetic=True) + + +def test_drain_closes_admission(harness): + client = harness.client() + client.activate_policy(activated_states(), "1") + client.drain() + with pytest.raises(ServiceRolloutError) as exc: + client.submit_group(group_request()) + assert exc.value.status == 409 and exc.value.code == "admission_closed" + assert not harness.runtime.registry.accepting + + +@pytest.mark.parametrize("cancel_whole_run", [False, True]) +def test_cancel_and_query_after_timeout(tmp_path, cancel_whole_run): + release = threading.Event() + + async def slow_executor(config, runtime_, request): + if request["group_index"] == 0: + while not release.is_set(): + await asyncio.sleep(0.05) # cancellable wait + return write_group_irs(tmp_path, request) + while True: # the second group blocks until it is cancelled + await asyncio.sleep(0.05) + + harness = ServiceHarness(tmp_path, slow_executor, group_wait_timeout=0.2) + try: + client = harness.client() + client.activate_policy(activated_states(), "1") + request = group_request() + # Short server wait: the response must say running and hint a query. + pending = harness.service.submit_group(harness.run_id, request) + assert pending["status"] in ("queued", "running") + assert "hint" in pending + status = client.group_status("group-0") + assert status["status"] in ("queued", "running") + release.set() + deadline = time.time() + 5 + while time.time() < deadline and status["status"] in ("queued", "running"): + time.sleep(0.05) + status = client.group_status("group-0") + assert status["status"] == "complete" + + request2 = group_request(group_index=1) + submitted = harness.service.submit_group(harness.run_id, request2) + assert submitted["status"] in ("queued", "running") + if cancel_whole_run: + result = harness.service.cancel_run(harness.run_id) + assert result["groups"] == {"group-0": "complete", "group-1": "cancelled"} + cancelled = client.group_status("group-1") + else: + cancelled = client.cancel_group("group-1") + assert cancelled["status"] == "cancelled" + with pytest.raises(ServiceRolloutError, match="cancelled|not complete"): + load_group_result(harness.run_id, request2, cancelled) + finally: + release.set() + harness.close() + + +def test_register_run_conflict_on_different_manifest(tmp_path): + async def executor(config, runtime_, request): + return write_group_irs(tmp_path, request) + + harness = ServiceHarness(tmp_path, executor) + try: + from harborrl.rollout.service import ServiceError + + same_manifest = {"run_id": "run-1", "group_size": 2} + assert harness.service.register_run("run-1", same_manifest)["existed"] + with pytest.raises(ServiceError) as exc: + harness.service.register_run("run-1", {"run_id": "run-1", "x": 1}) + assert exc.value.status == 409 + finally: + harness.close() + + +def test_service_group_request_and_run_service_groups(tmp_path, monkeypatch): + """Full SlimeAdapter path: fake Slime samples -> HTTP -> IRs bound to samples.""" + from harborrl.backends.slime_v032 import rollout as slime_rollout + + calls = [] + + async def executor(config, runtime_, request): + calls.append(request["group_id"]) + return write_group_irs(tmp_path, request) + + harness = ServiceHarness(tmp_path, executor) + try: + monkeypatch.setenv("HARBORRL_ROLLOUT_SERVICE_URL", harness.url) + monkeypatch.setenv("HARBORRL_ROLLOUT_SERVICE_TOKEN", harness.token) + monkeypatch.setenv("RUN_ID", harness.run_id) + monkeypatch.setenv("RUN_DIR", str(tmp_path)) + + class Sample: + class Status: + COMPLETED = "completed" + + def __init__(self, group_index): + self.metadata = {"native_task": dict(TASK)} + self.group_index = group_index + + args = SimpleNamespace(rollout_max_response_len=SAMPLING["max_new_tokens"], + rollout_temperature=1.0, rollout_top_p=1.0, + rollout_top_k=-1, rollout_stop=[], + sglang_router_ip="127.0.0.1", sglang_router_port=30000) + group = [Sample(0), Sample(0)] + monkeypatch.setenv("HARBORRL_ROLLOUT_SERVICE_TOKEN", harness.token) + # The adapter announces serving and derives the admitted version from + # the live pool itself before requesting groups. + from harborrl.backends.slime_v032 import rollout_service_client + + monkeypatch.setattr(rollout_service_client, "fetch_pool_states", + lambda endpoint, **kwargs: activated_states()) + + with pytest.raises(ServiceRolloutError, match='synthetic_evidence'): + asyncio.run(slime_rollout.run_service_groups(args, [group], 0)) + assert all('native_ir' not in sample.metadata for sample in group) + assert calls == ["group-0"] + assert harness.service.runs[harness.run_id].serving == ( + "http://127.0.0.1:30000") + + request = service_group_request( + group, slime_rollout.sampling_params(args), 0, "1") + assert request["group_id"] == "group-0" + assert request["group_index"] == 0 + assert [slot["slot_id"] for slot in request["slots"]] == ["0", "1"] + finally: + harness.close() + + +def test_announce_factory_attaches_config_and_runtime(tmp_path): + """A GPU announce through the factory makes the run executable alone.""" + async def executor(config, runtime_, request): + assert config is runtime_.config + return write_group_irs(tmp_path, request) + + service = RolloutService(run_root=tmp_path, executor=executor, allow_synthetic=True, + group_wait_timeout=5, + runtime_factory=lambda run_id, router: SimpleNamespace( + registry=FakeRegistry(), rollout_root=tmp_path, + manifest_path=lambda ident: manifest_path_from(tmp_path, ident), + config=SimpleNamespace(drain_timeout=1))) + try: + registration = service.register_run("run-9", {"run_id": "run-9"}) + result = service.announce_serving("run-9", "http://gpu:30000/") + assert result["runtime"] == "attached" + assert service.runs["run-9"].serving == "http://gpu:30000" + assert service.runs["run-9"].config is not None + service.activate_policy("run-9", activated_states(), "1") + submitted = service.submit_group("run-9", group_request()) + assert submitted["status"] == "complete" + assert registration["run_token"] + finally: + service.close() + + +def test_service_group_request_rejects_mixed_groups(): + class Sample: + def __init__(self, group_index, task): + self.metadata = {"native_task": dict(task)} + self.group_index = group_index + + other = dict(TASK) + other["revision"] = "2" + with pytest.raises(ValueError, match="mixes task locks"): + service_group_request([Sample(0, TASK), Sample(0, other)], dict(SAMPLING), 0, "1") + with pytest.raises(ValueError, match="group identity"): + service_group_request([Sample(0, TASK), Sample(1, TASK)], dict(SAMPLING), 0, "1") diff --git a/tests/harborrl/test_sandbox.py b/tests/harborrl/test_sandbox.py new file mode 100644 index 000000000..b98d27197 --- /dev/null +++ b/tests/harborrl/test_sandbox.py @@ -0,0 +1,97 @@ +"""Sandbox routing and fail-closed capacity contracts, without live resources.""" +import asyncio +import json +from pathlib import Path + +import pytest +import yaml +from test_native_training import native_config + +from harborrl.config.native import load_config +from harborrl.rollout.harbor_job.sandbox_config import ( + SandboxAdmission, + load_sandbox, + task_binding, +) + + +def settings(tmp_path): + return {'domain': 'https://sandbox.example', 'credential_file': 'key', + 'bindings': [{'image': 'registry.example/task@sha256:' + 'a' * 64, + 'max_live_instances': 1}], + 'installation': {'mode': 'offline_binary', 'version': '2.1.0', 'binary': 'claude', 'sha256': 'b' * 64}} + + +def test_local_runner_opt_in_and_unknown_fields(tmp_path): + path, _ = native_config(tmp_path) + old = load_config(path) + assert old['harbor']['workers'] == ['worker-a', 'worker-b'] + local = load_config(path, ['harbor.runner_transport=local', 'harbor.workers=[]', + 'harbor.environment={runtime: sandbox, config: sandbox.yaml}']) + assert local['harbor']['environment']['config'] == str(tmp_path / 'sandbox.yaml') + with pytest.raises(ValueError, match='local runner'): + load_config(path, ['harbor.runner_transport=local']) + with pytest.raises(ValueError, match='fields'): + load_config(path, ['harbor.environment={runtime: sandbox, config: x, api_key: secret}']) + + +def test_sandbox_binding_and_secret_rejection(tmp_path): + cfg = settings(tmp_path) + path = tmp_path / 'sandbox.yaml' + path.write_text(yaml.safe_dump(cfg)) + result = load_sandbox(path) + assert result['credential_file'] == str(tmp_path / 'key') + (tmp_path / 'task.toml').write_text('[environment]\ndocker_image = "' + cfg['bindings'][0]['image'] + '"\n') + assert task_binding(result, tmp_path)['image'] == cfg['bindings'][0]['image'] + (tmp_path / 'task.toml').write_text('[environment]\ndocker_image = "wrong"\n') + with pytest.raises(ValueError, match='exact binding'): + task_binding(result, tmp_path) + cfg['api_key'] = 'must-not-be-serialized' + path.write_text(yaml.safe_dump(cfg)) + with pytest.raises(ValueError, match='fields'): + load_sandbox(path) + + +def test_admission_across_groups_and_cleanup_debt(tmp_path): + async def run(): + cfg = {'max_live_instances': 2, 'bindings': [{'image': 'image', 'max_live_instances': 1}]} + admission = SandboxAdmission(cfg) + active, peak = 0, 0 + + async def trial(index): + nonlocal active, peak + async with admission.lease(cfg['bindings'][0], tmp_path / str(index)): + active += 1 + peak = max(peak, active) + await asyncio.sleep(0.01) + active -= 1 + await asyncio.gather(*(trial(i) for i in range(6))) + assert peak == 1 + root = tmp_path / 'failed' + root.mkdir() + async with admission.lease(cfg['bindings'][0], root): + (root / 'cleanup-required.json').write_text('{}') + with pytest.raises(RuntimeError, match='blocked'): + async with admission.lease(cfg['bindings'][0], tmp_path): + pytest.fail('must not allocate') + asyncio.run(run()) + + +def test_sandbox_launch_freezes_nonsecret_runtime(tmp_path): + from harborrl.backends.slime_v032.config import launch_plan + from harborrl.data.harbor.native_inspector import inspect_native + path, task = native_config(tmp_path) + cfg = settings(tmp_path) + (tmp_path / 'sandbox.yaml').write_text(yaml.safe_dump(cfg)) + (task / 'task.toml').write_text('[environment]\ndocker_image = "' + cfg['bindings'][0]['image'] + '"\n') + catalog = tmp_path / 'catalog.json' + entries = json.loads(catalog.read_text()) + entries[0]['task_digest'] = inspect_native(task)['task_digest'] + catalog.write_text(json.dumps(entries)) + config = load_config(path, ['harbor.runner_transport=local', 'harbor.workers=[]', + 'harbor.environment={runtime: sandbox, config: sandbox.yaml}']) + plan = launch_plan(config) + runtime = json.loads(plan['environment']['HARBORRL_NATIVE_SANDBOX']) + assert runtime['installation']['version'] == '2.1.0' + assert Path(runtime['installation']['binary']).is_absolute() + assert plan['environment']['HARBORRL_NATIVE_RUNNER_WORKERS'] == '[]' diff --git a/tests/harborrl/test_sandbox_allocation_transport.py b/tests/harborrl/test_sandbox_allocation_transport.py new file mode 100644 index 000000000..db34cf098 --- /dev/null +++ b/tests/harborrl/test_sandbox_allocation_transport.py @@ -0,0 +1,24 @@ +import asyncio + +import httpx + +from harborrl.rollout.harbor_job.sandbox_transport import SandboxStartupTransport + + +def test_generic_transport_observes_allocation_without_retry(): + async def run(): + calls, allocations = [], [] + + def handler(request): + calls.append(request.method) + return httpx.Response(200, json={'id': 'sbx-one'}) if request.method == 'POST' else httpx.Response(503) + + transport = SandboxStartupTransport('https://sandbox.example/v1', allocations.append, + transport=httpx.MockTransport(handler)) + async with httpx.AsyncClient(transport=transport) as client: + await client.post('https://sandbox.example/v1/sandboxes') + result = await client.get('https://sandbox.example/v1/sandboxes/sbx-one/endpoints/44772') + assert result.status_code == 503 + assert calls == ['POST', 'GET'] and allocations == ['sbx-one'] + + asyncio.run(run()) diff --git a/tests/harborrl/test_sandbox_cleanup.py b/tests/harborrl/test_sandbox_cleanup.py new file mode 100644 index 000000000..7364ac483 --- /dev/null +++ b/tests/harborrl/test_sandbox_cleanup.py @@ -0,0 +1,36 @@ +import asyncio + +import httpx +import pytest + +pytest.importorskip('harbor.environments.opensandbox') + +from harborrl.rollout.harbor_job.environments.opensandbox import ( + ManagedOpenSandboxEnvironment, +) + + +@pytest.mark.parametrize('delete_status,expected', [(204, 'deleted'), (500, 'delete_failed')]) +def test_allocated_id_cleanup_requires_confirmed_absence(tmp_path, monkeypatch, delete_status, expected): + calls = [] + + async def handler(request): + calls.append((request.method, request.url.path)) + assert request.url.path == '/v1/sandboxes/sbx-owned' + if request.method == 'DELETE': + return httpx.Response(delete_status) + return httpx.Response(200 if len(calls) == 1 else 404) + + client_class = httpx.AsyncClient + monkeypatch.setattr(httpx, 'AsyncClient', lambda **kwargs: client_class( + **kwargs, transport=httpx.MockTransport(handler))) + env = object.__new__(ManagedOpenSandboxEnvironment) + env.cfg = {'domain': 'https://platform.test', 'cleanup_timeout_s': 1} + env.binding = {'image': 'owned'} + env._api_key = 'test-only' + env.evidence = tmp_path + env.record = {'sandbox_id': 'sbx-owned', 'runtime_digest': 'test', 'events': []} + asyncio.run(env._cleanup_allocated()) + assert env.record['state'] == expected + assert (tmp_path / 'cleanup-required.json').exists() == (expected == 'delete_failed') + assert calls[:2] == [('GET', '/v1/sandboxes/sbx-owned'), ('DELETE', '/v1/sandboxes/sbx-owned')] diff --git a/tests/harborrl/test_sandbox_portability.py b/tests/harborrl/test_sandbox_portability.py new file mode 100644 index 000000000..1a4c5bb6a --- /dev/null +++ b/tests/harborrl/test_sandbox_portability.py @@ -0,0 +1,57 @@ +"""Public core must remain usable without the optional SDK or cluster package.""" +import subprocess +import sys +from pathlib import Path + +import pytest +import yaml + +from harborrl.rollout.harbor_job.sandbox_config import load_sandbox, read_credential + + +def test_config_loading_does_not_import_optional_packages(tmp_path): + config = tmp_path / 'sandbox.yaml' + config.write_text(yaml.safe_dump({'domain': 'https://sandbox.example', 'credential_file': 'key', + 'bindings': [{'image': 'image', 'max_live_instances': 1}]})) + code = """ +import sys +from harborrl.rollout.harbor_job.sandbox_config import load_sandbox +cfg = load_sandbox(sys.argv[1]) +assert cfg['installation'] is None and cfg['options'] == {} +assert not any(k == 'harbor' or k.startswith(('harbor.', 'opensandbox', 'harborrl_h_cluster', 'torch', 'ray')) for k in sys.modules) +""" + subprocess.run([sys.executable, '-c', code, str(config)], check=True) + + +def test_core_has_no_cluster_imports_or_platform_keys(): + root = Path(__file__).resolve().parents[2] / 'harborrl' + for path in root.rglob('*.py'): + source = path.read_text() + assert 'harborrl_h_cluster' not in source, str(path) + assert 'brainbox' not in source.lower(), str(path) + assert 'environment_id' not in source, str(path) + + +@pytest.mark.parametrize('adapter', ['invalid', '../module:Class', 'module:Class:Other']) +def test_adapter_path_rejected_without_import(tmp_path, adapter): + path = tmp_path / 'sandbox.yaml' + path.write_text(yaml.safe_dump({'domain': 'https://sandbox.example', 'credential_file': 'key', + 'bindings': [{'image': 'image', 'max_live_instances': 1}], 'adapter': adapter})) + with pytest.raises(ValueError, match='module:Class'): + load_sandbox(path) + + +def test_generic_token_does_not_require_platform_key_format(tmp_path): + path = tmp_path / 'credential' + path.write_text('test-only\n') + path.chmod(0o600) + assert read_credential({'credential_file': str(path)}) == 'test-only' + + +def test_nested_provider_secrets_never_enter_runtime_manifest(tmp_path): + path = tmp_path / 'sandbox.yaml' + path.write_text(yaml.safe_dump({'domain': 'https://sandbox.example', 'credential_file': 'key', + 'bindings': [{'image': 'image', 'max_live_instances': 1}], + 'options': {'nested': {'api_key': 'test-only'}}})) + with pytest.raises(ValueError, match='secrets'): + load_sandbox(path) diff --git a/tests/harborrl/test_sandbox_runtime.py b/tests/harborrl/test_sandbox_runtime.py new file mode 100644 index 000000000..9f896818f --- /dev/null +++ b/tests/harborrl/test_sandbox_runtime.py @@ -0,0 +1,92 @@ +"""Shared Sandbox scheduling and service lifecycle contracts.""" +import asyncio +import json +from dataclasses import replace +from types import SimpleNamespace + +import pytest +from test_deployment_config import deployment_mapping + +from harborrl.backends.slime_v032.rollout_service_client import ( + RolloutServiceClient, + ServiceRolloutError, +) +from harborrl.deployment.schema import validate +from harborrl.rollout.harbor_job.sandbox_config import SandboxAdmission +from harborrl.rollout.runtime import RolloutRuntimeConfig, run_slot + + +def test_sandbox_deployment_allows_local_or_remote_runners(tmp_path): + cfg = deployment_mapping(tmp_path) + cfg['rollout'] = {'provider': 'sandbox', 'workers': []} + assert validate(cfg)['rollout']['workers'] == [] + cfg['rollout']['workers'] = deployment_mapping(tmp_path)['rollout']['workers'] + assert validate(cfg)['rollout']['workers'] + + +def test_slow_group_is_posted_once_even_after_transport_timeout(monkeypatch): + client = RolloutServiceClient('http://manager', 'run', 'token', poll_interval=0) + calls = [] + def request(method, path, body=None): + calls.append(method) + if method == 'POST': + raise ServiceRolloutError('timeout', code='transport') + return {'status': 'running' if len(calls) == 2 else 'complete', 'result': {}} + monkeypatch.setattr(client, '_request', request) + assert client.submit_group({'group_id': 'group-0'})['status'] == 'complete' + assert calls == ['POST', 'GET', 'GET'] + + +def test_sandbox_cleanup_debt_prevents_retry(tmp_path, monkeypatch): + from harborrl.rollout import runtime + from harborrl.rollout.harbor_job import sandbox_config + profile = tmp_path / 'profile.json' + profile.write_text('{}') + binding = {'image': 'image', 'max_live_instances': 1} + sandbox = {'max_live_instances': 1, 'bindings': [binding]} + config = RolloutRuntimeConfig('run', str(tmp_path), '', str(profile), '127.0.0.1', 1, + 'http://gateway', '/python', (), {}, 2, 128, 1024, 2, 2, 'tok', 'tpl', True, 1, + 'qwen3-8B', 'http://router', sandbox=sandbox, runner_transport='local') + current = SimpleNamespace(rollout_root=tmp_path, semaphore=asyncio.Semaphore(2), + registry=None, profile={}, sandbox_admission=SandboxAdmission(sandbox)) + calls = [] + async def attempt(identity, **kwargs): + calls.append(kwargs) + assert kwargs['runner_host'] is None + assert kwargs['sandbox'] == sandbox + kwargs['root'].mkdir(parents=True) + (kwargs['root'] / 'cleanup-required.json').write_text('{}') + raise RuntimeError('deletion unconfirmed') + monkeypatch.setattr(runtime, 'run_attempt', attempt) + monkeypatch.setattr(sandbox_config, 'task_binding', lambda *args: binding) + entry = {'id': 'task', 'path': '/task', 'task_digest': 'digest', 'reward_profile': {}} + with pytest.raises(RuntimeError, match='cleanup unresolved'): + asyncio.run(run_slot(config, current, 0, 0, 0, entry, 'sampling', '1')) + assert len(calls) == 1 and current.sandbox_admission.blocked + restored = replace(config, workers=('worker',), runner_transport='ssh') + assert json.loads(restored.environment()['HARBORRL_NATIVE_SANDBOX']) == sandbox + + +def test_group_failure_waits_for_sibling_cleanup(tmp_path, monkeypatch): + from harborrl.rollout import runtime + events = [] + async def attempt(identity, **kwargs): + if identity.slot_id == '0': + await asyncio.sleep(.01) + raise RuntimeError('failed slot') + try: + await asyncio.sleep(60) + finally: + await asyncio.sleep(.01) + events.append('cleanup complete') + monkeypatch.setattr(runtime, 'run_attempt', attempt) + config = SimpleNamespace(run_id='run', group_size=2, max_attempts=1, workers=(), + task_worker_overrides={}, runner_transport='local', sandbox=None, + gateway_url='http://gateway', runner_python='/python', max_output_tokens=128) + current = SimpleNamespace(profile={}, rollout_root=tmp_path, + semaphore=asyncio.Semaphore(2), registry=None) + entry = {'id': 'task', 'path': '/task', 'task_digest': 'digest', 'reward_profile': {}} + with pytest.raises(RuntimeError, match='exhausted its retry budget'): + asyncio.run(runtime.run_group(config, current, entry=entry, + group_index=0, rollout_id=0, sampling_digest='x', policy_version='1')) + assert events == ['cleanup complete'] diff --git a/tests/harborrl/test_sandbox_scale.py b/tests/harborrl/test_sandbox_scale.py new file mode 100644 index 000000000..2e6f39e2a --- /dev/null +++ b/tests/harborrl/test_sandbox_scale.py @@ -0,0 +1,36 @@ +"""A saturated image pool must not reserve unrelated global capacity.""" +import asyncio +from contextlib import suppress + +from harborrl.rollout.harbor_job.sandbox_config import SandboxAdmission + + +def test_waiting_binding_does_not_starve_other_environments(tmp_path): + async def run(): + first = {'image': 'first', 'max_live_instances': 1} + second = {'image': 'second', 'max_live_instances': 1} + admission = SandboxAdmission({'max_live_instances': 2, 'bindings': [first, second]}) + entered, release = asyncio.Event(), asyncio.Event() + + async def waiting(): + entered.set() + async with admission.lease(first, tmp_path / 'waiting'): + await release.wait() + + async with admission.lease(first, tmp_path / 'active'): + task = asyncio.create_task(waiting()) + try: + await entered.wait() + async def independent(): + async with admission.lease(second, tmp_path / 'independent'): + pass + await asyncio.wait_for(independent(), 1) + finally: + task.cancel() + with suppress(asyncio.CancelledError): + await task + # Cancellation of a queued request must not leak either admission permit. + async with admission.lease(first, tmp_path / 'after'), admission.lease(second, tmp_path / 'also-after'): + pass + + asyncio.run(run()) diff --git a/tests/harborrl/test_service_group_concurrency.py b/tests/harborrl/test_service_group_concurrency.py new file mode 100644 index 000000000..3cbf4c9be --- /dev/null +++ b/tests/harborrl/test_service_group_concurrency.py @@ -0,0 +1,63 @@ +import asyncio +import threading +from types import SimpleNamespace + +import pytest + +from harborrl.backends.slime_v032 import rollout, rollout_service_client + + +@pytest.mark.parametrize('fail', [False, True]) +def test_service_groups_overlap_preserve_order_and_await_failure_cleanup(monkeypatch, fail): + barrier = threading.Barrier(2, timeout=3) + cancelled, completed = threading.Event(), threading.Event() + activated, failed = threading.Event(), threading.Event() + + class Client: + def __init__(self, *args): + self.run_id = 'run' + + def announce_serving(self, _): + pass + + def activate_policy(self, *_): + activated.set() + + def submit_group(self, request): + assert activated.is_set() + barrier.wait() + if fail and request['group_id'] == 'first': + failed.set() + raise ValueError('group failed') + if fail: + assert failed.wait(3) + assert not cancelled.is_set() + completed.set() + return {'result': {'id': request['group_id']}} + + def cancel_run(self): + cancelled.set() + + def load_result(run_id, request, result, *, allow_synthetic): + assert allow_synthetic is False + return [result['id']] + + monkeypatch.setenv('HARBORRL_ROLLOUT_SERVICE_URL', 'http://localhost') + monkeypatch.setenv('RUN_ID', 'run') + monkeypatch.delenv('HARBORRL_ROLLOUT_SERVICE_TOKEN_FILE', raising=False) + monkeypatch.setattr(rollout_service_client, 'RolloutServiceClient', Client) + monkeypatch.setattr(rollout_service_client, 'fetch_pool_states', lambda _: [{'weight_version': '1'}]) + monkeypatch.setattr(rollout_service_client, 'service_group_request', lambda group, *_: {'group_id': group}) + monkeypatch.setattr(rollout_service_client, 'load_group_result', load_result) + monkeypatch.setattr(rollout, 'lock_policy_version', lambda _: '1') + monkeypatch.setattr(rollout, 'sampling_params', lambda _: {}) + monkeypatch.setattr(rollout, 'bind_group_samples', lambda *_: None) + args = SimpleNamespace(sglang_router_ip='localhost', sglang_router_port=8000) + if fail: + with pytest.raises(ValueError, match='group failed'): + asyncio.run(rollout.run_service_groups(args, ['first', 'second'], 0)) + assert cancelled.is_set() + else: + assert asyncio.run(rollout.run_service_groups(args, ['first', 'second'], 0)) == [['first'], ['second']] + assert not cancelled.is_set() + assert completed.is_set() diff --git a/tests/harborrl/test_slime_v032_exporter.py b/tests/harborrl/test_slime_v032_exporter.py index d93e84746..41ce72ef9 100644 --- a/tests/harborrl/test_slime_v032_exporter.py +++ b/tests/harborrl/test_slime_v032_exporter.py @@ -221,6 +221,46 @@ def test_pure_clipped_loss_numerics(): assert LOSS_SCALE_CORRECTION == 1.0 +def test_postprocess_accepts_multiple_groups_and_partial_actor_shards(tmp_path): + args = SimpleNamespace(n_samples_per_prompt=2) + groups = native_groups(tmp_path) + batch = export_training_batch(groups, rollout_ids=[10, 11]) + data = convert_samples_to_train_data(args, fake_samples(batch, groups)) + rollout_data_postprocess(args, 0, data) + shard = {key: values[::2] for key, values in data.items()} + rollout_data_postprocess(args, 0, shard) + + +def test_postprocess_rejects_individual_group_overweight(): + args = SimpleNamespace(n_samples_per_prompt=2) + data = { + "tokens": [[1, 2]] * 4, + "response_lengths": [1] * 4, + "rewards": [0.0] * 4, + "raw_reward": [0.0] * 4, + "truncated": [0] * 4, + "sample_indices": list(range(4)), + "rollout_ids": [10, 10, 10, 11], + "loss_masks": [[1]] * 4, + "rollout_log_probs": [[-0.2]] * 4, + "rollout_mask_sums": [1, 1, 1, 2], + } + # Total weight is below two, but group 10 exceeds its own budget. + with pytest.raises(ValueError, match="budget.*10"): + rollout_data_postprocess(args, 0, data) + + +@pytest.mark.parametrize("group_id", [-1, 0.5, float("nan"), "invalid"]) +def test_postprocess_rejects_invalid_group_id(tmp_path, group_id): + args = SimpleNamespace(n_samples_per_prompt=2) + groups = native_groups(tmp_path) + batch = export_training_batch(groups, rollout_ids=[10, 11]) + data = convert_samples_to_train_data(args, fake_samples(batch, groups)) + data["rollout_ids"][0] = group_id + with pytest.raises(ValueError, match="rollout id"): + rollout_data_postprocess(args, 0, data) + + def test_postprocess_rejects_drift(): args = SimpleNamespace(n_samples_per_prompt=2) base = { @@ -265,7 +305,7 @@ def __float__(self): "raw_reward": [Scalar()], "truncated": [IntegerScalar()], "sample_indices": [0], - "rollout_ids": [0], + "rollout_ids": [IntegerScalar()], "loss_masks": [[1]], "rollout_log_probs": [[Scalar()]], "rollout_mask_sums": [IntegerScalar()], @@ -402,3 +442,45 @@ def test_export_package_has_no_backend_imports(): else: continue assert not (names & forbidden), f"{path} imports backend dependencies: {names & forbidden}" + + +@pytest.mark.parametrize('group_index, expected_advantages, expected_counts', [ + (0, [-0.5 / 0.500001, 0.5 / 0.500001], [2, 6]), + (1, [0.0, 0.0], [4, 2]), +]) +def test_legacy_export_preserves_grpo_record_semantics( + tmp_path, group_index, expected_advantages, expected_counts, +): + from harborrl.rollout.exporters.native import export_group + group = native_groups(tmp_path)[group_index] + exported = export_group(group, 2) + assert exported['export_version'] == 'native-grpo-v2' + assert exported['advantages'] == expected_advantages + assert exported['reduction'] == 'sum_token_weighted_loss; no secondary whitening' + assert exported['synthetic'] is False + for ir, advantage, count in zip(group, expected_advantages, expected_counts): + records = [record for record in exported['records'] + if record['identity']['trajectory_id'] == ir['identity']['trajectory_id']] + assert len(records) == len(ir['turns']) + for index, (turn, record) in enumerate(zip(ir['turns'], records)): + n = len(turn['output_ids']) + assert record == { + 'identity': ir['identity'], 'turn_index': index, + 'tokens': turn['input_ids'] + turn['output_ids'], + 'prompt_length': len(turn['input_ids']), 'response_length': n, + 'loss_mask': [0] * len(turn['input_ids']) + [1] * n, + 'old_logprobs': turn['logprobs'], 'advantages': [advantage] * n, + 'token_weights': [1.0 / (2 * count)] * n, + 'training_reward': ir['evaluation']['training_reward'], 'padding': False, + } + + +def test_native_task_inspection_avoids_legacy_conversion_imports(): + import subprocess + import sys + code = ( + 'import sys; import harborrl.data.harbor.native_inspector; ' + 'assert "harborrl.data.harbor.inspector" not in sys.modules; ' + 'assert "harborrl.data.harbor.materializer" not in sys.modules' + ) + subprocess.run([sys.executable, '-c', code], check=True) diff --git a/tests/harborrl/test_webui.py b/tests/harborrl/test_webui.py new file mode 100644 index 000000000..ddb397ffd --- /dev/null +++ b/tests/harborrl/test_webui.py @@ -0,0 +1,244 @@ +"""HTTP and process lifecycle contracts without allocating training resources.""" +import http.client +import json +import subprocess +import sys +import threading +import time +from pathlib import Path + +import pytest +import yaml +from test_options import answers as options_answers + +from harborrl.options.wizard import build_config_bundle +from harborrl.webui.cli import workspace_lock +from harborrl.webui.jobs import Jobs +from harborrl.webui.server import Console, make_server + +answers = options_answers + + +@pytest.fixture +def console(tmp_path): + instance = Console(tmp_path / "web") + yield instance + instance.jobs.close() + + +@pytest.fixture +def server(console): + server = make_server(console) + thread = threading.Thread(target=server.serve_forever) + thread.start() + yield server + server.shutdown() + thread.join(5) + server.server_close() + + +def request(server, console, method, path, body=None, headers=None): + connection = http.client.HTTPConnection("127.0.0.1", server.server_port, timeout=5) + defaults = {"Authorization": "Bearer " + console.token, "Content-Type": "application/json", + "Origin": f"http://127.0.0.1:{server.server_port}"} + connection.request(method, path, body=json.dumps(body) if body is not None else None, + headers={**defaults, **(headers or {})}) + response = connection.getresponse() + payload = response.read() + result = (response.status, dict(response.getheaders()), payload) + connection.close() + return result + + +def test_static_and_bootstrap(server, console): + for path in ("/", "/app.js", "/style.css", "/lucide.min.js", "/brand.jpg"): + status, headers, payload = request(server, console, "GET", path) + assert status == 200 and payload + assert "frame-ancestors 'none'" in headers["Content-Security-Policy"] + assert console.token.encode() not in payload + status, _, payload = request(server, console, "GET", "/api/bootstrap") + assert status == 200 + assert json.loads(payload)["catalog"]["catalog_version"] == 1 + + +@pytest.mark.parametrize("headers", [{"Authorization": "Bearer invalid"}, {"Origin": "https://external.example"}, + {"Host": "external.example"}, {"Content-Type": "text/plain"}]) +def test_request_boundary(server, console, headers): + status, _, _ = request(server, console, "POST", "/api/configurations", {}, headers=headers) + assert status in {403, 415} + assert not console.list_configurations() + + +@pytest.mark.parametrize("name", ["../outside", "/absolute", "", "a/b", "a\\b", "a b", ".hidden"]) +def test_output_paths(console, answers, name): + with pytest.raises(ValueError): + console.save({"name": name, "answers": answers}) + + +def test_preview_save_and_snapshot(server, console, answers): + body = {"name": "experiment", "answers": answers, "check_files": True} + status, _, payload = request(server, console, "POST", "/api/preview", body) + assert status == 200, payload + assert json.loads(payload)["validation"]["status"] == "valid_with_deferred_checks" + assert not console.directory("experiment").exists() + status, _, payload = request(server, console, "POST", "/api/configurations", body) + assert status == 201, payload + assert request(server, console, "POST", "/api/configurations", body)[0] == 400 + status, _, payload = request(server, console, "POST", "/api/jobs", {"configuration": "experiment", "mode": "dry-run"}) + assert status == 201, payload + job = json.loads(payload) + for _ in range(600): + record = console.jobs.list()[0] + if record["state"] != "running": + break + time.sleep(.05) + assert record["state"] == "succeeded", console.jobs.log(job["id"]) + snapshot = Path(record["snapshot"]) + training = yaml.safe_load((snapshot / "config.yaml").read_text()) + deployment = yaml.safe_load((snapshot / "deployment.yaml").read_text()) + assert training["output"]["root"] == deployment["storage"]["run_root"] == record["output_root"] + saved = console.directory("experiment") / "config.yaml" + assert yaml.safe_load(saved.read_text())["output"]["root"] != record["output_root"] + assert json.loads(console.jobs.log(job["id"])["text"])["config"]["schema_version"] == 2 + + +@pytest.mark.parametrize("mode", ["train", "doctor", "shell"]) +def test_live_requires_confirmation(console, answers, mode, monkeypatch): + console.save({"name": "test", "answers": answers}) + def forbidden(*args, **kwargs): + raise AssertionError("Unconfirmed live job must not execute") + monkeypatch.setattr(subprocess, "Popen", forbidden) + with pytest.raises(ValueError): + console.start({"configuration": "test", "mode": mode}) + + +def test_process_lifecycle(console, answers, tmp_path, monkeypatch): + console.save({"name": "test", "answers": answers}) + real_popen = subprocess.Popen + calls = [] + def stand_in(command, **kwargs): + calls.append(command) + return real_popen([sys.executable, "-u", "-c", "import time; print('training stand-in', flush=True); time.sleep(30)"], **kwargs) + monkeypatch.setattr(subprocess, "Popen", stand_in) + job = console.start({"configuration": "test", "mode": "train", "confirmed": True}) + assert calls[0][4] == "train" and "--dry-run" not in calls[0] + with pytest.raises(ValueError, match="active"): + console.start({"configuration": "test", "mode": "dry-run"}) + for _ in range(600): + if console.jobs.log(job["id"])["text"]: + break + time.sleep(.05) + child = console.jobs.child + stopped = console.jobs.stop(job["id"]) + assert stopped["state"] == "stopping" + child.wait(timeout=5) + assert console.jobs.list()[0]["state"] == "stopped" + assert console.jobs.log(job["id"])["text"] + recovered = Jobs(console.jobs.root) + assert recovered.list()[0]["state"] == "stopped" + + +def test_restart_blocks_unknown_job(console, answers, tmp_path): + root = console.jobs.root / "abc" + root.mkdir() + (root / "job.json").write_text(json.dumps({"id": "abc", "state": "running", "created_at": "2026-10-22"})) + recovered = Jobs(console.jobs.root) + assert recovered.list()[0]["state"] == "unknown" + with pytest.raises(ValueError, match="interrupted"): + recovered.start("test", build_config_bundle(answers, output_directory=tmp_path / "out"), "dry-run") + recovered.reconcile("abc") + assert recovered.list()[0]["state"] == "interrupted" + + +def test_workspace_lock(tmp_path): + with workspace_lock(tmp_path), pytest.raises(ValueError, match="Another console"), workspace_lock(tmp_path): + pass + with workspace_lock(tmp_path): + pass + + +def test_symlink_cannot_escape(console, tmp_path, answers): + outside = tmp_path / "outside" + outside.mkdir() + (console.configurations / "escape").symlink_to(outside, target_is_directory=True) + with pytest.raises(ValueError): + console.save({"name": "escape", "answers": answers}) + assert not list(outside.iterdir()) + + +def test_failed_spawn_and_log_limit(console, answers, tmp_path, monkeypatch): + def failed(*args, **kwargs): + raise OSError("injected launch failure") + monkeypatch.setattr(subprocess, "Popen", failed) + bundle = build_config_bundle(answers, output_directory=tmp_path / "out") + job = console.jobs.start("test", bundle, "dry-run") + assert job["state"] == "failed" + Path(job["log_path"]).write_bytes(b"x" * 100000) + result = console.jobs.log(job["id"]) + assert len(result["text"]) == 65536 and result["truncated"] + training_log = Path(job["output_root"]) / "training/one/train-stdout.log" + training_log.parent.mkdir(parents=True) + training_log.write_text("step=1 reward=0.5") + assert console.jobs.log(job["id"])["training_text"] == "step=1 reward=0.5" + + +def test_import_and_bad_body(server, console): + status, _, payload = request(server, console, "POST", "/api/import", {"text": "answers_version: 1\npreset: embedded-local"}) + assert status == 200 and json.loads(payload)["answers"]["preset"] == "embedded-local" + assert request(server, console, "POST", "/api/import", {"text": "[]"})[0] == 400 + assert request(server, console, "GET", "/../../pyproject.toml")[0] == 404 + + +@pytest.mark.parametrize("text", ["answers_version: true\npreset: embedded-local", "answers_version: 1\npreset: embedded-local\ntraining: []", "answers_version: 1\npreset: embedded-local\nunknown: x"]) +def test_import_rejects_invalid_answers(server, console, text): + assert request(server, console, "POST", "/api/import", {"text": text})[0] == 400 + + +def test_preview_never_starts_process(server, console, answers, monkeypatch): + def forbidden(*args, **kwargs): + raise AssertionError("Preview must not start a process") + monkeypatch.setattr(subprocess, "Popen", forbidden) + assert request(server, console, "POST", "/api/preview", {"name": "offline", "answers": answers, "check_files": True})[0] == 200 + + +def test_local_stop_reaches_descendants(tmp_path): + import os + import signal + + from harborrl.config import ROOT + script = ''' +import os, signal, subprocess, sys, time +from pathlib import Path +from harborrl.deployment.training_target import LocalTrainingTarget +root = Path(sys.argv[1]) +worker = "import signal,sys,time; from pathlib import Path; signal.signal(signal.SIGINT, lambda *a: (Path(sys.argv[1]).write_text('stopped'), sys.exit(0))); Path(sys.argv[1]+'.ready').touch(); time.sleep(30)" +parent = "import subprocess,sys,time; from pathlib import Path; child=subprocess.Popen([sys.executable,'-c',sys.argv[1],sys.argv[2]]); Path(sys.argv[2]+'.pid').write_text(str(child.pid)); time.sleep(30)" +target = LocalTrainingTarget([sys.executable, '-c', parent, worker, str(root / 'worker')], cwd=root, env=os.environ.copy()) +target.launch() +(root / 'group').write_text(str(target.child.pid)) +sys.exit(target.wait()) +''' + env = {**os.environ, "PYTHONPATH": str(ROOT)} + proc = subprocess.Popen([sys.executable, "-c", script, str(tmp_path)], env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + try: + for _ in range(600): + if (tmp_path / "worker.ready").exists(): + break + time.sleep(.05) + assert (tmp_path / "worker.ready").exists() + proc.send_signal(signal.SIGINT) + proc.wait(timeout=10) + for _ in range(100): + if (tmp_path / "worker").exists(): + break + time.sleep(.05) + assert (tmp_path / "worker").read_text() == "stopped" + finally: + if (tmp_path / "group").exists(): + try: + os.killpg(int((tmp_path / "group").read_text()), signal.SIGKILL) + except ProcessLookupError: + pass + if proc.poll() is None: + proc.kill() + proc.wait(timeout=10) diff --git a/tests/test_consistency.py b/tests/test_consistency.py new file mode 100644 index 000000000..04146beb5 --- /dev/null +++ b/tests/test_consistency.py @@ -0,0 +1,81 @@ +"""Dependency-free tests of consistency evidence and admission decisions.""" +import struct + +import pytest + +from harborrl.consistency.config import require_raw_sampling, validate +from harborrl.consistency.metrics import merge, statistics +from harborrl.consistency.report import assess, decide +from harborrl.consistency.sglang import check_pool, score +from harborrl.consistency.slime import evidence_key + + +def test_weighted_statistics_ignore_padding_and_tp_duplicates(): + settings = validate({'mode': 'enforce', 'max_abs_delta': 1}) + a = assess([([-1], [-2], [1])], settings) + b = assess([([-2, -2, -100], [-2, -2, -1], [1, 1, 0])], settings) + duplicate = {**a, 'contribute': False} + report = decide([a, b, duplicate], settings, phase='test') + assert report['status'] == 'passed' + assert report['metrics']['token_count'] == 3 + assert report['metrics']['mean_abs_delta'] == pytest.approx(1 / 3) + assert report['metrics']['p99_abs_delta_upper'] == 1 + + +@pytest.mark.parametrize('rows', [[], [([-1], [-1], [0])], [([float('nan')], [-1], [1])], + [([-1], [-1, -2], [1])], [([-1], [-1], [2])]]) +def test_structural_errors_fail_audit(rows): + settings = validate({'mode': 'audit'}) + assert decide([assess(rows, settings)], settings, phase='test')['status'] == 'failed' + + +def test_thresholds_are_explicit_and_sampling_is_raw(): + with pytest.raises(ValueError): + validate({'mode': 'enforce'}) + with pytest.raises(ValueError): + require_raw_sampling({'temperature': .7, 'top_p': 1}) + settings = validate({'mode': 'audit', 'max_abs_delta': .01}) + assert decide([assess([([-1], [-2], [1])], settings)], settings, phase='test')['status'] == 'warning' + + +def test_serving_evidence_survives_float32_conversion(): + q = [-1.123456789] + converted = [struct.unpack('!f', struct.pack('!f', q[0]))[0]] + assert evidence_key([1, 2], q, [1]) == evidence_key([1, 2], converted, [1]) + assert evidence_key([1, 3], q, [1]) != evidence_key([1, 2], converted, [1]) + + +def transport(url, payload=None): + if payload is None: + return {'weight_version': '1'} + assert payload['sampling_params']['max_new_tokens'] == 0 + assert payload['input_ids'] == [10, 11, 12] + return {'meta_info': {'weight_version': '1', 'input_token_logprobs': [[None, 10], [-1., 11], [-2., 12]]}} + + +def test_teacher_forcing_exact_response_boundary(): + assert score('http://engine', [10, 11], [12], '1', transport=transport) == [-2.] + with pytest.raises(ValueError, match='version'): + score('http://engine', [10, 11], [12], '2', transport=transport) + + +def test_engine_canary_does_not_dilute_bad_engine(): + settings = validate({'mode': 'enforce', 'max_abs_delta': 2, 'max_mean_abs_delta': .6}) + reference = {'weight_version': '1', 'cases': [{'prompt_ids': [10, 11], 'response_ids': [12], 'logprobs': [-1.]}]} + states = [{'endpoint': 'http://bad', 'healthy': True, 'weight_version': '1'}, + {'endpoint': 'http://good', 'healthy': True, 'weight_version': '1'}] + def serving(url, payload=None): + result = transport(url, payload) + if payload and 'good' in url: + result['meta_info']['input_token_logprobs'][-1][0] = -1. + return result + report = check_pool(states, reference, settings, transport=serving) + assert report['metrics']['mean_abs_delta'] == .5 + assert report['status'] == 'failed' + + +def test_histogram_merge_is_token_weighted(): + parts = [statistics([([-1.] * n, [-1. - delta] * n, [1] * n)]) for n, delta in [(1, .1), (9, .01)]] + result = merge(parts) + assert result['mean_abs_delta'] == pytest.approx(.019) + assert result['p95_abs_delta_upper'] >= .1 - 1e-12 diff --git a/tests/test_consistency_torch.py b/tests/test_consistency_torch.py new file mode 100644 index 000000000..0f81e6660 --- /dev/null +++ b/tests/test_consistency_torch.py @@ -0,0 +1,31 @@ +"""Distributed rejection tests; optional Torch dependency.""" +from pathlib import Path + +import pytest + +from harborrl.consistency.config import validate +from harborrl.consistency.report import assess, distributed_gate + +torch = pytest.importorskip('torch') + + +def rank_failure(rank, rendezvous, root): + from datetime import timedelta + + import torch.distributed as dist + dist.init_process_group('gloo', init_method=f'file://{rendezvous}', rank=rank, world_size=2, timeout=timedelta(seconds=30)) + try: + settings = validate({'mode': 'enforce', 'max_abs_delta': .01}) + part = assess([([-1.], [-1. if rank == 0 else float('nan')], [1])], settings) + with pytest.raises(ValueError, match='rejected'): + distributed_gate(part, settings, Path(root) / 'failure.json', phase='injected-rank-failure') + (Path(root) / f'rank{rank}.ok').write_text('rejected') + finally: + dist.destroy_process_group() + + +def test_one_rank_error_rejects_all_ranks(tmp_path): + import torch.multiprocessing as mp + mp.spawn(rank_failure, args=(str(tmp_path / 'rendezvous'), str(tmp_path)), nprocs=2, join=True) + assert (tmp_path / 'rank0.ok').exists() + assert (tmp_path / 'rank1.ok').exists() diff --git a/uv.lock b/uv.lock index d5e9dd73d..4efd07ea6 100644 --- a/uv.lock +++ b/uv.lock @@ -1,34 +1,976 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", + "python_full_version < '3.12'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/4c/bdccd81e9ee225b69c60e7766c9a5b05364f118f4d383713b89a682d772d/aiohttp-3.14.5.tar.gz", hash = "sha256:5558a7f5a05af9ecf744af91e5baefc436f93c9333e656c27ec253f9a6bbe178", size = 8078052, upload-time = "2026-10-11T01:05:12.408Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/82/f7f0652e3a845e095d8505a2807a0fe5782abb71f99b2f516c93ff0be492/aiohttp-3.14.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef692a24087a699c0a4a26af45e746e0c1eae2116f6d8a5ff91d8aae2b867b45", size = 834351, upload-time = "2026-10-11T00:59:07.668Z" }, + { url = "https://files.pythonhosted.org/packages/95/c5/10cb0a5195bc3ec05a74ee494f8431a15e559800f2e869f3526ad4a16f04/aiohttp-3.14.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1220353657ad49493551f089ce02f1a348fd57ffd585bfec77f2f3c4fe3a7346", size = 560663, upload-time = "2026-10-11T00:59:09.853Z" }, + { url = "https://files.pythonhosted.org/packages/32/dc/b7fefdd7dd64d080da2d69d609344191cc94b0e2eba70200c371014c9b36/aiohttp-3.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:330900acd0dc4cb8b27f9c127fbaad770964845338493e7906ae3822e82dbf8d", size = 553263, upload-time = "2026-10-11T00:59:11.5Z" }, + { url = "https://files.pythonhosted.org/packages/61/5f/3271cad1b34347e8bf27abee20d501ebdc56cec97e8ae59cd3fabb23d917/aiohttp-3.14.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0684952aeae1f5dbfe02d46039338513b94009baecd15d8e4098a357c4c4a2a6", size = 1843813, upload-time = "2026-10-11T00:59:13.143Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/2011b30fe61889af112ad380e6fd44a93cca4b51296f612cac666c177882/aiohttp-3.14.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d94e44be379e569758fee8a9a58431cfc3c2598c708b92b1cfe96c66b4c94aef", size = 1811243, upload-time = "2026-10-11T00:59:15.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ba/72f3ac523ed48d38cdfb169f1b2a2e93ce06d9fcf4f46d0f5692e9539bcc/aiohttp-3.14.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5af42135fdfebdadbc2bcd9c0842a48ccf0d62794c36a260b21dc4b94d1e0119", size = 1898815, upload-time = "2026-10-11T00:59:17.224Z" }, + { url = "https://files.pythonhosted.org/packages/e6/69/bbf24f3b555ea1befa7db1e2510c728c8b0e80e61ce360beb7d94bc562b7/aiohttp-3.14.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f07fe3ac408d8b3f768be471dc3f56d43843c47d97c66120534467a15ead197", size = 2022390, upload-time = "2026-10-11T00:59:19.244Z" }, + { url = "https://files.pythonhosted.org/packages/5d/38/01f5fc732d5373d6c099db0343279cd4e6607287d336bab6ff6ae9085a0d/aiohttp-3.14.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f375db73a39f5cf83696d500e21a67f418dc9a988955756f254be8f03b7b3651", size = 1856329, upload-time = "2026-10-11T00:59:21.19Z" }, + { url = "https://files.pythonhosted.org/packages/15/85/1f86fbd8fe34ad447c63e679404a1d5be435fe256a9d281b791fc1ed0d35/aiohttp-3.14.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8df7d481654ac96fe1ba9a02a9f67770fdd367823e0d5ef01b922725c4bd2cfa", size = 1712763, upload-time = "2026-10-11T00:59:22.982Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/ced51cf47e427e7aa6142c3bf6f33022472703bc9061289329532d3c40c0/aiohttp-3.14.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e95c8def4b81c5d68d5cf1f54c07acd7c0d2577af244e5b6da802120825737c6", size = 1812125, upload-time = "2026-10-11T00:59:25.032Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f1/9610b4e263b554ca4191501ba72b952efe06556b647da74887abc5d41049/aiohttp-3.14.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2ebb54b3f932210503072f09974b4fb574d823e497a944adfdcd140a6a00255", size = 1829550, upload-time = "2026-10-11T00:59:27.078Z" }, + { url = "https://files.pythonhosted.org/packages/ac/8a/1d1fb27cf9fa99b13133de33044c4d10d7705be0d4173d6d56abad1b67f9/aiohttp-3.14.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:038c2c7e8caa26b6c8423779b5eaf1893904048a512c19b32fe841ffa5592b50", size = 1868179, upload-time = "2026-10-11T00:59:29.315Z" }, + { url = "https://files.pythonhosted.org/packages/24/9f/3cfafa86ff025623dcca0d30335db7b8a6f3fa348148441208147e827e04/aiohttp-3.14.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:b7806e804889231b0e06469fd4a5c06313d1c0a3377322b6d9237fa5e0fe4167", size = 1697721, upload-time = "2026-10-11T00:59:31.161Z" }, + { url = "https://files.pythonhosted.org/packages/62/1d/86439a23bf32296e715d8c75535f0cd701821d36719324baef65bcd6af9e/aiohttp-3.14.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9bab2045550c4fe0f7baf89574db1b455c195750702ba96fef1f16972b146617", size = 1887000, upload-time = "2026-10-11T00:59:33.329Z" }, + { url = "https://files.pythonhosted.org/packages/1d/0f/6044f2d02b3874966d84cd119f9b8eda46aa2991230078aea4dd17750cc9/aiohttp-3.14.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96a2e584f0b9ed8f1fa33211397dcf67bb7069866402cb405d191c2f0defb9a3", size = 1842020, upload-time = "2026-10-11T00:59:35.384Z" }, + { url = "https://files.pythonhosted.org/packages/18/64/b49d2a34428ff937631b07dde74d5879a7a2c8559aab39331d40769e8cb6/aiohttp-3.14.5-cp310-cp310-win32.whl", hash = "sha256:602c1e9b718a3275c580149f947e7fac65044c0a20e599553fb12e9700da9eca", size = 489282, upload-time = "2026-10-11T00:59:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7c/1114e723f1c4a65597820b605a0ddcd7f3b28fdfde3ac8d520d517ca582c/aiohttp-3.14.5-cp310-cp310-win_amd64.whl", hash = "sha256:bea559ad70218d230663e4210875735076a9bfea5994cef34a55a25faeaf2544", size = 512021, upload-time = "2026-10-11T00:59:38.7Z" }, + { url = "https://files.pythonhosted.org/packages/48/97/e08c646f4bad63d77876b24591d013d7f33f02fda7c931eafe3224f38467/aiohttp-3.14.5-cp310-cp310-win_arm64.whl", hash = "sha256:dca3fa8d8a0a26679862eccb0b1a9151b2b9f1cd2c212e7a6335778faaff5833", size = 493016, upload-time = "2026-10-11T00:59:40.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f3/8997f18890a92c79f77fcfbb4f78ca17c2d4ab109e9eb6d31b4e9de194a0/aiohttp-3.14.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d51db97c96384fbfcaf8f4c65922183a68b94f891c3c10c862ef5f6df2adbb1f", size = 832358, upload-time = "2026-10-11T00:59:42.195Z" }, + { url = "https://files.pythonhosted.org/packages/8e/42/084651e9efb5cadb99265f786df60f2a7b353cead7bfd2c87003cb4867ad/aiohttp-3.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ae53924aa853a7a2ca20ed4142c7c6b56338e4d4cd999e2980075b9efc2e257a", size = 559636, upload-time = "2026-10-11T00:59:43.677Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/92838944e601f0fe195fd3f3ada37e29fbee3d19bfd323fe15937ec79d36/aiohttp-3.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2c473a355f9239efcb72c92d5abfd8fcdb0cc78c8e9af607e72ca12dbb36593", size = 552588, upload-time = "2026-10-11T00:59:45.592Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/dd9b95c5c20ceae1b47738e656bd79e9883e3117a9cc8b143901eb604e9a/aiohttp-3.14.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e8fa6644e541fcd7e02430588c7fc93b602c1778ea0bc345505db76b61cfb4", size = 1928224, upload-time = "2026-10-11T00:59:47.294Z" }, + { url = "https://files.pythonhosted.org/packages/c6/47/70010cd2ba8746968d53d433ff328c33f06a66c99da5f3de759c64a6b2eb/aiohttp-3.14.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:579f97d5120f2971876d2ddca2968135f6944d00c44c3a6590ad7d86ca9b403f", size = 1877674, upload-time = "2026-10-11T00:59:49.472Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a1/b026f071dbd87f0bdba86b92d47e46f3899e3ad7143b6e0fe7e7887090b4/aiohttp-3.14.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:24409db442e2fb6e766bc7f3943851a8381dec3098140e43bb2e843b79e31b12", size = 1985850, upload-time = "2026-10-11T00:59:51.296Z" }, + { url = "https://files.pythonhosted.org/packages/36/d1/f7b6f6f8c3cb5a71b53baeddc2e70c224c28b12a23a55c42994c504019b7/aiohttp-3.14.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5e8f97c0488ffda3082766ac0f2c8150a9a58c4d05788330e479cfd449b37939", size = 2105471, upload-time = "2026-10-11T00:59:53.191Z" }, + { url = "https://files.pythonhosted.org/packages/42/74/2a2b22953de15c6c8a80debf26db3047e4e8a5a5db6dd7c2c6c01eec0605/aiohttp-3.14.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50a195903119008fe9cc68710535eb37f556ffffd6a7759afe70a2c145587045", size = 1929915, upload-time = "2026-10-11T00:59:55.609Z" }, + { url = "https://files.pythonhosted.org/packages/33/ad/f80e8d33933d0eacd0217efa3b7fb32c9f48ed480ed0db53cf9948bf474f/aiohttp-3.14.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0133c3c3b54a0bf1e71fa5c1ad95c93f07fd54e24ef1fe182f5122e1573d2bf1", size = 1773832, upload-time = "2026-10-11T00:59:57.611Z" }, + { url = "https://files.pythonhosted.org/packages/04/a4/0273d239f3e3bb69438f209c64c82e1f98dc60d5db264795e41f7dd05dcb/aiohttp-3.14.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c172db893e516e1358e65a95ee20b7ce7173963eefe318b6ab2a2220688b999e", size = 1901001, upload-time = "2026-10-11T01:00:00.605Z" }, + { url = "https://files.pythonhosted.org/packages/16/ed/7dd439d26c654645bc34ca3c9a822fc11c1c51801fed1cefdfe1cc41c1f6/aiohttp-3.14.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f2a7966bda23dd85051f1661ce0ace38d6890e05ec6c357ecae9d2479cba377e", size = 1907218, upload-time = "2026-10-11T01:00:02.699Z" }, + { url = "https://files.pythonhosted.org/packages/32/dd/86249c3b8248562a17fd3e22ee0c164378d65766f01aceef62cf52783710/aiohttp-3.14.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4887d130a7bbfed3a85493bb5a25e5b5b558d40c1d986dd16970d2bb26d63793", size = 1958650, upload-time = "2026-10-11T01:00:04.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/ca4d53d68f683ce195807fd0aeb063bf6d1c6b39343fff234c9524e8d5a9/aiohttp-3.14.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:225c579c23b68b343cccea27a7e06e3bd8ec23a09c30b427eb3f1e4ca6239b20", size = 1760057, upload-time = "2026-10-11T01:00:06.978Z" }, + { url = "https://files.pythonhosted.org/packages/6b/42/005437ffd7fa56c2ce3347add40404b654a32754ce91f2e2a14b9e5ab2bc/aiohttp-3.14.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ab52d8f1fc1b64821c1fbad64a647ed6203627004059a6d1ed4f0858a1499703", size = 1978081, upload-time = "2026-10-11T01:00:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/3a5b66fe1b7b23d3c6524350817ed55de5feb7fbb4cccba7cbe044d118dd/aiohttp-3.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb131d775a1573c1aee66656bd78b023577bbdb6cb8349a07773bd4f73e68a6e", size = 1922954, upload-time = "2026-10-11T01:00:11.035Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/d08a554f281d42fd7c6b2b6dd7738f3e3a7a9f657ddd87e11ca2091cdfe3/aiohttp-3.14.5-cp311-cp311-win32.whl", hash = "sha256:e87046c8ff77a8decdb6a41d8ab25824b47531b2da933aeab0c1e21c7acff329", size = 488101, upload-time = "2026-10-11T01:00:12.933Z" }, + { url = "https://files.pythonhosted.org/packages/0f/15/52b5e65f02b33686ae49f1518548ae4f5a7adaf31d9d3c54fa12a143137b/aiohttp-3.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:6f275c11d1aa6d4c458e05a68be084efe3c55a113d99e3f46a318098e52948fc", size = 512575, upload-time = "2026-10-11T01:00:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b9/bb75012635f3defb0f7cbb7c4ae391e36bb9cb3fbdca3b9944e2ebebf743/aiohttp-3.14.5-cp311-cp311-win_arm64.whl", hash = "sha256:b032a0023eb41d768ce77d83210ab2a3c389bc0b09313273c7e1eca48c10a755", size = 493179, upload-time = "2026-10-11T01:00:16.961Z" }, + { url = "https://files.pythonhosted.org/packages/d5/94/6ba86efddcb616c811b40e6a0dfdd862738f647e4e3860a961075d5e9ed8/aiohttp-3.14.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df37b620684e19b5e25724412518ccafc3b1a49cdac706fdbd2f983fad943450", size = 817499, upload-time = "2026-10-11T01:00:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/7bca6d84dabd228aa8eb4b7f9feac2586aaa9be5505d7d65bf287a615c43/aiohttp-3.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef60869969180ec2464f1349aff07138ae35ca2200f0946cb3552e49e8f301a8", size = 549467, upload-time = "2026-10-11T01:00:20.854Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/11b89f45ca486252dd67dd5f3231fec04bf5518da39a95cb3997619f17fb/aiohttp-3.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d079c0a0135c36e7beb6f1c88087c8f108dc5891cdd0b5eafa778421bda70ed2", size = 548067, upload-time = "2026-10-11T01:00:22.659Z" }, + { url = "https://files.pythonhosted.org/packages/22/ff/c6615806c14aab34f82b9424ccde8ce6e417315fd57ce1c5b4d4747888e1/aiohttp-3.14.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abfda5cb094a829f7bc25216a32f7db2e85cc65bd59910f8e7b40b3d9b224764", size = 1896245, upload-time = "2026-10-11T01:00:24.638Z" }, + { url = "https://files.pythonhosted.org/packages/39/b2/25a8c971ae6a92c8394d77e42422d7f38989e05cf41a5ceb92d73d67ab7e/aiohttp-3.14.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9cc882cf8619109583c906b4d4a85d6a111a98afa34b7a450d1e08118d016820", size = 1872180, upload-time = "2026-10-11T01:00:26.838Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d5/99f93ea36cc5205e47c1e5a803e087f2ad21b5430b5db2e942cb6e988a37/aiohttp-3.14.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7457580535e019e1247ea35d6a02bf081ad30c26d0cbc210c93f6c3ab67a0835", size = 1937261, upload-time = "2026-10-11T01:00:28.74Z" }, + { url = "https://files.pythonhosted.org/packages/40/a6/9ac9c9e6695040bd73d2584a1b59a9388f6433c76d5294a7bf591e21ffe5/aiohttp-3.14.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5ed596aedb9c42afd3fe0aae3117725378ac73d2cc5ddc735056fbdb96c5d02", size = 2064994, upload-time = "2026-10-11T01:00:30.623Z" }, + { url = "https://files.pythonhosted.org/packages/da/e4/aa172eb534b7f02f1f8ff1c3213347eaf3cf218db91a727c6863c22f1035/aiohttp-3.14.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20f085697d7e911f1f73c43ed03fafbed1e7121797e2eb5428efa80398060584", size = 1919630, upload-time = "2026-10-11T01:00:32.548Z" }, + { url = "https://files.pythonhosted.org/packages/06/7d/4eedafc5bababa8932636c141e346806966eade12c0b7e5946d43bf8218b/aiohttp-3.14.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:74b0a9c8270f9b0a11410e124ff8d4f18bfc1f1837440ec84da5ae7b50927b5d", size = 1710016, upload-time = "2026-10-11T01:00:34.471Z" }, + { url = "https://files.pythonhosted.org/packages/b2/94/eee018537ba19da0ceb2ac79cab83faed4ac49568e08376e2799043f2538/aiohttp-3.14.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:19e2ba471507c34f8252402ab50f5ab512398b9ea8c8f1cb26beb3f75793ba30", size = 1858112, upload-time = "2026-10-11T01:00:36.581Z" }, + { url = "https://files.pythonhosted.org/packages/68/76/354653a306547238f3427283905972d796ba7c292ba9977abec9f2b6f260/aiohttp-3.14.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d418ce2af40c6bb685b3f663e9e8de27cb0a22431d8e88a167348d7f01878073", size = 1896371, upload-time = "2026-10-11T01:00:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ec/63e8c7136b570e356345ad3174e3820fdc973ea10712cb6c649bf875755a/aiohttp-3.14.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:70cb4008ac2ed1e0ca9e824deb4b53d3aa0d939109698ebf1e723a84337bd794", size = 1905836, upload-time = "2026-10-11T01:00:40.527Z" }, + { url = "https://files.pythonhosted.org/packages/57/d8/11365bda144b127928cd42533d0eff78a55613c9e28f81941bd6630ea887/aiohttp-3.14.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a23fe35d776bc03cb495938b9594450d047e3bc08c5255315a82323e9cb7d2dd", size = 1696534, upload-time = "2026-10-11T01:00:42.686Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3d/82df0461b18e00b2998f205c03e0d3010222478c43640aceb8e03dcd7e8f/aiohttp-3.14.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3e0eb43bed3c6801a6cee315195377789e90b2a72c2277a475b578535312488d", size = 1933970, upload-time = "2026-10-11T01:00:44.71Z" }, + { url = "https://files.pythonhosted.org/packages/03/ad/6ddfe0aacd931c17b53533336d97e9d11a98b96d6ae815a9da0b19f82ccf/aiohttp-3.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be7dd397d64ca3e1869626fa9318aaebb54b7bf93bc72d7a205448d83e4f748", size = 1899501, upload-time = "2026-10-11T01:00:46.629Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8e/189bdd9ae4793059bb09f6dc880f211a6c6c7859cebcbf33912dbfab7dd7/aiohttp-3.14.5-cp312-cp312-win32.whl", hash = "sha256:eb324e2009fb54db30a071dad7caf6998ee2879c4704007efb244514dad1fec1", size = 480370, upload-time = "2026-10-11T01:00:48.468Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ce/1f08114679d49655b30a6e0a29858375c94b82c1de1a0bd0a20c2fee8b02/aiohttp-3.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:2cc38a4f2b516bef1714e690df87a0e043faf1a7693c82d860091684453d5111", size = 506141, upload-time = "2026-10-11T01:00:50.272Z" }, + { url = "https://files.pythonhosted.org/packages/79/d4/c7b4f60b16a1b7e43249fa9031ae05e7e8c341ba4b7d1866f914dafeaa0e/aiohttp-3.14.5-cp312-cp312-win_arm64.whl", hash = "sha256:a63afd1f757de949028387e65a7127b61ad0f775432dbb0e62816ae619fe69ac", size = 488108, upload-time = "2026-10-11T01:00:52.321Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e1/2841e020ebb7aefae5513586193e011e06313d9a6bdbd296622afbbce204/aiohttp-3.14.5-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:9ad7e6aa38c20da1be697874349c4c273c8a03b7887169665081706398d0439a", size = 538932, upload-time = "2026-10-11T01:00:54.3Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a6/7fb8ea8fe96bcc7b7c7a36d10f021d99d01a8dc8a4b3f0ddacecfad9a80e/aiohttp-3.14.5-cp313-cp313-android_24_x86_64.whl", hash = "sha256:f59c7673465908cbe506117176156c127f29f917677afceada34957179221d91", size = 553329, upload-time = "2026-10-11T01:00:56.442Z" }, + { url = "https://files.pythonhosted.org/packages/de/64/d056e3c27647dc25af1a592cf356245382ea7c808171b9dac7677afedfc8/aiohttp-3.14.5-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1b5416552740edf07234cc9437d0706f2acb67b93c198670b1a68e1b2b587dec", size = 521367, upload-time = "2026-10-11T01:00:58.345Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e4/95226147e11d4db916fd1d495dcf85af8e3816e38333e42718241196e848/aiohttp-3.14.5-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:43351bdb5e4c3cb7d1772368e988534e869a74db7778079a83782c11c69535c7", size = 528322, upload-time = "2026-10-11T01:01:00.211Z" }, + { url = "https://files.pythonhosted.org/packages/17/cd/1d3c9192cafdb51cad62b2d3ded96cff9cc8af51893210aadd448a325389/aiohttp-3.14.5-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:c8c4478bef6d57fcfda15dae461ea3c9f06aa7b257c58df3f2300174ccbb185a", size = 539874, upload-time = "2026-10-11T01:01:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0c/dfa33aecc7d4d1dc75e05248f5eac5a0edf4d09e7b44d93ab62529b0c1db/aiohttp-3.14.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c2c30484dd1417ef98b51021ffa2cc0d7f3c78918adaaaab7e70817335ab3e02", size = 813836, upload-time = "2026-10-11T01:01:04.01Z" }, + { url = "https://files.pythonhosted.org/packages/33/17/4a63738052d20567d55529d6daa1b9480d906fd52930fbcf6d3fbed618f0/aiohttp-3.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dab9ac5a67c8d1f070c00fa8fccb7cbd1b8dcc1a8d6b42f37540df9b3d4cc603", size = 548070, upload-time = "2026-10-11T01:01:06.035Z" }, + { url = "https://files.pythonhosted.org/packages/15/e5/b57e58695a757fd4c02497c033fced96a69c631b13866c43d336530c9670/aiohttp-3.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e1cc2bfaee8c214f06080a7c7d5772419b8a1108e8e5349236189811823fb02a", size = 545786, upload-time = "2026-10-11T01:01:07.817Z" }, + { url = "https://files.pythonhosted.org/packages/9a/68/8c2c67a3aedf46e00f3c42f04fbc6983de80d4ed5786151e33681ba45883/aiohttp-3.14.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74efb69332b85675b1eabd760a8cfc2e2cf42c60607c66f88014c1bdfb40942d", size = 1886492, upload-time = "2026-10-11T01:01:09.834Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4b/74aab5e8d28c62e8f795b4fe8f38cf5586fd264a9a27bd2141ef6490333d/aiohttp-3.14.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42b5e616946dbaf505e2bff18c9af2cd4ef9e7ef300ee58a6e951a5b7cf147ae", size = 1870057, upload-time = "2026-10-11T01:01:12.045Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f7/eafc3b1988302b1815d9fd4a21071be5c360d616c0a430d02fd92dc97688/aiohttp-3.14.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f9033b43f511f27547c557dcaba0177649e10a3725336ccd2cce0fdc1dc4850d", size = 1925119, upload-time = "2026-10-11T01:01:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/a1/04/78d8f294f74dd570f3898ff20402349fce524176045df98ba727d6846a68/aiohttp-3.14.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15a310d3c71398e3d7bfc93a1a73fbe664315cd9e9016b8efc1cff85eeab7155", size = 2053137, upload-time = "2026-10-11T01:01:16.344Z" }, + { url = "https://files.pythonhosted.org/packages/32/51/395d225ef36f5a50d8e548dcd3141bfdbcd31fb6eed859022c573d2c4d66/aiohttp-3.14.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ffa3a523a36d8628f98c06492ae16a31a23d14c0b4ec721757b477319f656d6", size = 1911038, upload-time = "2026-10-11T01:01:18.653Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a4/2aec1aa06d82e8a244843b5dae31d78061e5e76744270a86dd0ee051c889/aiohttp-3.14.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fb6a6e919bfb703227bc1ce6579281b84b1a2ba57deb9794dfdbec7dcd1e40c", size = 1702205, upload-time = "2026-10-11T01:01:20.904Z" }, + { url = "https://files.pythonhosted.org/packages/16/27/6051bfde7b6f418f70edd60d655fa426abb3355fa0981764739d87ecf160/aiohttp-3.14.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:43e1b7994a8b038125f722bff07492ef501110722c2727c408995d9fb864c421", size = 1848588, upload-time = "2026-10-11T01:01:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/9e/44/55efc06fc26c4e6e1c095f231b4c222bf2d86d64a8eebbc24b2bb5958ea8/aiohttp-3.14.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5f3e96071686755d9cd3600c3880183eb94b012178e92746d68101800f0ed8a3", size = 1887540, upload-time = "2026-10-11T01:01:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/dc/1502bfdc2a65760d386ac6a00090b0addaa8a3c9c60b3f8127fad3a9afb2/aiohttp-3.14.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cd88b01f3d37b7a2a34f91d98f14720206f1ea3d540843fab2d649dd5fb91fec", size = 1895030, upload-time = "2026-10-11T01:01:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/93/7e/44174bb6288264418c9eec07a5e35180969c0d5a796c7af544db3cb8a33a/aiohttp-3.14.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f2ed8b64dc0c651c0f5a9c926777719770021251b8f336d97c4b80b660836ce1", size = 1693244, upload-time = "2026-10-11T01:01:29.39Z" }, + { url = "https://files.pythonhosted.org/packages/47/dd/b507d64e50db23888582fff08eda13998b12f9dea70c072edaac19218380/aiohttp-3.14.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a9918e58faf62ba2c7147927d06057aec78f42475aff5048047ec47e7265a600", size = 1926308, upload-time = "2026-10-11T01:01:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/5b51b4f63e3f2793151f4aea49c48fe1e00baeb7cec9c7a206de499f8de0/aiohttp-3.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:42f320d4a5b00b9af0bddcfec5407dc6f2d9816f006b2f79ebbaa31f16895df3", size = 1888406, upload-time = "2026-10-11T01:01:33.715Z" }, + { url = "https://files.pythonhosted.org/packages/ff/13/d5e818a5eaba9f822016727299f41c0e1075799f82a6433ec508a93ab867/aiohttp-3.14.5-cp313-cp313-win32.whl", hash = "sha256:3ae800a20947e2c2e53088047d021e6bf7d51560cc49f6a0737a1f79d2e3a13c", size = 479970, upload-time = "2026-10-11T01:01:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d0/8eca2c65aa467320990d78fb2005f38ed3588944280c39ff9deb6423fef1/aiohttp-3.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:d05e94cdfe0d15d0206f970722d2554780ce562787b21b218b275447f8751319", size = 505985, upload-time = "2026-10-11T01:01:37.574Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f8/4cdd65305d2fca14b886bea9ed2abb1fe726287872524e56e3d26692b47d/aiohttp-3.14.5-cp313-cp313-win_arm64.whl", hash = "sha256:f001b571ead90ca1770f1e616db255351a1703317f20374c361ef22f12c06d09", size = 487629, upload-time = "2026-10-11T01:01:39.477Z" }, + { url = "https://files.pythonhosted.org/packages/43/be/3184a1d34a8be665569eadb7e9e764b4629e4f3413e241cb2e4d6fecf3b3/aiohttp-3.14.5-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:939042d5cda21d41a6f512e7cc8b8e33a2aebff863352251da495fbd91b673b5", size = 540673, upload-time = "2026-10-11T01:01:41.354Z" }, + { url = "https://files.pythonhosted.org/packages/60/2a/d35f3ba4cf157b072e3b674bf9983047ca5ea5173c995d32d877e1191d36/aiohttp-3.14.5-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6da32b5ff3fd78d244e37300463434c7145162bfd2b6e9e915ab164da37f7343", size = 554087, upload-time = "2026-10-11T01:01:43.719Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d2/61a33880ca4eaca95a9c60ca3f6beed15555af1028652dfaac601627787b/aiohttp-3.14.5-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1b438b73c38111818d0c9d6a5c2bfed8584c8e503a49ef085d70e874ec846738", size = 522613, upload-time = "2026-10-11T01:01:46.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/de579b299d2225dc2c6fd99d579d91f16c02a913fb5af9a3cf2fe9bd88ba/aiohttp-3.14.5-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:755933b107ea7a6a9ac916f635a70595a5b1a32fac10a8ff0b9f2ab88555550c", size = 529682, upload-time = "2026-10-11T01:01:48.477Z" }, + { url = "https://files.pythonhosted.org/packages/f4/4a/ddb923564e15e053b6e060b0036e1694dcadcb13aa476c5a87dcad20e336/aiohttp-3.14.5-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b3cc509327c7b27f6f4727a8830f4004f6df7766e179f2f4b8e54e65c0bec5d3", size = 540120, upload-time = "2026-10-11T01:01:50.474Z" }, + { url = "https://files.pythonhosted.org/packages/3d/36/a640fbecaa53727a5900b892bdbe17b5f3e8cc88903e22864fe41b654def/aiohttp-3.14.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7bd8ac754ebd6733a3e2a0dd1674c4d8ab086196803fd8dcd776f07b4e2607d9", size = 818795, upload-time = "2026-10-11T01:01:52.665Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b6/d52ca608859e271b5fa7944074802dc45f60e52c318a4ddc34edbf73586e/aiohttp-3.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e724a7b6091f0b1ac064f9d1b15ff9ec52e6033a86cdae649e5f086e32a3c0db", size = 549667, upload-time = "2026-10-11T01:01:54.65Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b5/05b8ac39a76ff4bca89f42a4c2471560c71f894ff6e4bc16158c951874e3/aiohttp-3.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c32e26310cc10e547f53cd13d39a369034f69dcb7d749d5cb0e5f67bc196b6ba", size = 548922, upload-time = "2026-10-11T01:01:56.547Z" }, + { url = "https://files.pythonhosted.org/packages/19/b0/5aa186d56ce2334dabe29b70bd99dc8ae926ee44184c0de64a53d415a4f3/aiohttp-3.14.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1eb8167961ec4dfcc8cb9dd50bd0ee72519f7ef496be95203e49e27b01618382", size = 1882422, upload-time = "2026-10-11T01:01:59.258Z" }, + { url = "https://files.pythonhosted.org/packages/db/f7/7d5c91bb9620db300c8ddb05337a9014301acb626223faff3abcb8ea47d7/aiohttp-3.14.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c1d60eafd9c7e8e74abd03a5b00df44e7febfe6d9b89b559c0a6551eef0699d4", size = 1843621, upload-time = "2026-10-11T01:02:01.417Z" }, + { url = "https://files.pythonhosted.org/packages/30/0a/b208953b96d8f24b75f6da704f508e6c5cf3022f52b60c61933df082e89c/aiohttp-3.14.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:137351bf20bbed9a65e839f4a4452ac377389bdb2f2857d2acffef38f5e9f2d1", size = 1927915, upload-time = "2026-10-11T01:02:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/f6/79/90ebcccb55e2d1e11a1fed581d83bb966e38fb35fb4b2577fdc980f8707a/aiohttp-3.14.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fba47bc2c3d7303c3d027c6cf4d07626c37b1314ac81f5820c31032e0ca1f677", size = 2036339, upload-time = "2026-10-11T01:02:06.046Z" }, + { url = "https://files.pythonhosted.org/packages/a6/66/55a8904b3a129fafdf94f9cc0a2e4ca09a3c914650be52355db7ad0bbdb6/aiohttp-3.14.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94684b879ac1d71e4238850c99b62dc1b28d9086b156a2555f082010b85a865c", size = 1888041, upload-time = "2026-10-11T01:02:08.384Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b8/96b25da7329a52e42c812b1e8b076386039ec4fc312afa043d173d8147fc/aiohttp-3.14.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56572c42e3ecd636de8d2c3dd54cf5fc939cb5c32eb56297f176a0d366fac622", size = 1703849, upload-time = "2026-10-11T01:02:10.903Z" }, + { url = "https://files.pythonhosted.org/packages/1b/43/fbf976e3ae4c038d6f5c84945ab2150298c2d71201674d4e53d158063e75/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a95529a92a446db351675f4aab518feaf5e99842f63f5dd17160c2b74f382db3", size = 1849703, upload-time = "2026-10-11T01:02:13.15Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7d/218e912f4c1d89bde7ac551409be57ad2f6121638e56942d395a7cb1fa58/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:3edbece0379b8b4aaa67619b8aa2399bb66fce372cd5911098a434ea77220aa0", size = 1852695, upload-time = "2026-10-11T01:02:15.295Z" }, + { url = "https://files.pythonhosted.org/packages/5a/42/252a1b9287e3b6e393a1c3bd1776f36af30f5f25f071bbf2b0cb7eba9116/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:56d9828f204331a5ca8850fcfe2bcce95a149f1f223f60cc7216e5524978e480", size = 1893493, upload-time = "2026-10-11T01:02:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/78/97/71cae83d5100556fad1521684f7cd1e3e578432644f850245ed3bd969310/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:20064a177a070d789ee64a50b01a9161d3468e989baacfc6c714aa685c4b332f", size = 1693468, upload-time = "2026-10-11T01:02:20.666Z" }, + { url = "https://files.pythonhosted.org/packages/1f/69/73d88e97a8b5f0ca7a946d0011c0de99fb188b1687c7948ecd0553dc5bf0/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:81c2b3dfd56c62bee6108e4852d5970b4cf9086390b6983f52b666e878c1f115", size = 1915398, upload-time = "2026-10-11T01:02:23.011Z" }, + { url = "https://files.pythonhosted.org/packages/05/f0/881644bcb15d4b258daea9b720a0af9dc4330496cc8d6ade9090cdd0cffc/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:09ec102b4b8c9a920275733bbc11fdbb615efe6f9231a06007c0218d336fb77a", size = 1871470, upload-time = "2026-10-11T01:02:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/7f/de/19d9ebbcce5aedaa3242d8a99ff8816a60bdb7629fb0084bf4a45bfd1f62/aiohttp-3.14.5-cp314-cp314-win32.whl", hash = "sha256:9c428eb2bd8817588d16a0ab898aa4eb5d141f896aa2b394cc79a4cf61d9a8e2", size = 485517, upload-time = "2026-10-11T01:02:27.579Z" }, + { url = "https://files.pythonhosted.org/packages/a9/74/8cdaf0e58c2588371670d5a9a8215bbb971d36940b6e5d051967dd05c07d/aiohttp-3.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:6f967dde489ca6a8c02d093ab245d2cbf50ccb5c36adf0188b17b0ca39d24b67", size = 511755, upload-time = "2026-10-11T01:02:29.685Z" }, + { url = "https://files.pythonhosted.org/packages/1a/6b/e0100e25502430a531c7cf1482a378d0b65bf728ab60c01ee270e56bc469/aiohttp-3.14.5-cp314-cp314-win_arm64.whl", hash = "sha256:1d2d981b53dd09a319e3570ef8cc3bbc3ef86f5a7abef0f6b2bff3867db3a9e7", size = 495151, upload-time = "2026-10-11T01:02:32.163Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c2/ca2ead7b655688c53c03aeeb6e96e6851c9ff08d6be7b13802f53a6ae8fd/aiohttp-3.14.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9ce66feae6ac65327379460380549bf1b8df8e17c4e25df2a2bcf168272e3bed", size = 855396, upload-time = "2026-10-11T01:02:34.443Z" }, + { url = "https://files.pythonhosted.org/packages/a7/70/22206fea409255a240c926ce11de48de354ae2bb90ca44f709c05497585d/aiohttp-3.14.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:27c2322e03f66101acb09869ce1cf1efc04994ee95e1735b69827bf8c8b9d781", size = 567734, upload-time = "2026-10-11T01:02:36.644Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e5/79a36c118308b56f8667d67e05d2fb6dc638ab45985704cdb199631bedaa/aiohttp-3.14.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff75a7537413a86e7cafe98e0e1d6e3dc4b15c6349896e7d5c6b881bfdb6d550", size = 567379, upload-time = "2026-10-11T01:02:38.757Z" }, + { url = "https://files.pythonhosted.org/packages/63/a3/2ebec7dece3b1f02c30d2e484647f6f7b13952b1bb40a4cb285b849e8432/aiohttp-3.14.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c061aa954daaf57d2a4b8374f9fca621ef0e1b603584431c220c22458c59b6d", size = 2050686, upload-time = "2026-10-11T01:02:41.169Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d2/7e4d093db2f4450482652e7ef19a9e19919028f5135aa52bc4078c3beb80/aiohttp-3.14.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1612fa5857b37bf32e5c1eaeefb96e3b01e9c70679eec81f0934e8a600080863", size = 1915077, upload-time = "2026-10-11T01:02:43.563Z" }, + { url = "https://files.pythonhosted.org/packages/a6/88/bd40d09958442a0a1df67da81de496361d6e2afc04f9db2950d835da750b/aiohttp-3.14.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adbeee7d6fd4cf5fe0aece2fb3edc4243615d3180430ba8149d01a90670cac99", size = 2027204, upload-time = "2026-10-11T01:02:46.185Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/3a601c1f8d3103c1a60ea981f20924855da9f575d2007fc38f8898b792fb/aiohttp-3.14.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2966998927d7bed9db12c0a4647b0c7b179755878fc9c357fe1ffd3e3b0c1a5", size = 2149520, upload-time = "2026-10-11T01:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/22/d0/4e41bfe1b1ce1cb6f6d2e59fa7a88ef5cf92c402b2d07e9018778ba9edbc/aiohttp-3.14.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e317e0fb6b16212c881d2205a7d87414c29acd69320b3aa6dce9d9c7b86fe4f", size = 2002039, upload-time = "2026-10-11T01:02:51.293Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/72067019545c502b881b031153c437752ecef48d7d213bc0218ccebb4bfb/aiohttp-3.14.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50343c1757b4b6f6708eeaf24534b32f19dfb99fb1b762c00420867a62fc81e0", size = 1786164, upload-time = "2026-10-11T01:02:53.533Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fe/7741efd6119bfb7a00827fe6f7b84b4409de58d888ae21adc5a9a6824992/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:083673c7a94c3ea035caaa5ca04288bdb44887abfe1f5ba23294e6a4b03efd2d", size = 1970689, upload-time = "2026-10-11T01:02:55.888Z" }, + { url = "https://files.pythonhosted.org/packages/43/e7/342a13bf67f34d269bf2f7e870ecd72b99c832cc6f0a271a9210c0ebfb84/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2528cb4c6b92008c76ac9ac6298624069bb2db91ff4929905512d1d84485f658", size = 1951967, upload-time = "2026-10-11T01:02:58.467Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d4/fdb3b27340617e5e64df18a70fa89097778652ea5c7c7e2f79def76999c3/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b1b8ece1e71132d2afba4dbc0c3d62c766e25165990b25db1196c04969eb3d84", size = 1968511, upload-time = "2026-10-11T01:03:00.883Z" }, + { url = "https://files.pythonhosted.org/packages/83/b2/e8f88298de78d1a951f36f9f966d38ec6ed1d4721303ba02064a545e8aa6/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fce9523df31cea6284f3e2c479876750d7687cf671d7b25d32b19effc0e86441", size = 1778497, upload-time = "2026-10-11T01:03:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/22/68/9ccdb93d664345c546be7f34480b921774d8c0d98f47e70d7e03b115d475/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:09e0eb18c7e0c8777e2f9149de63799195b9b3ca1b5c81ba6f32f2c6b8628210", size = 1999676, upload-time = "2026-10-11T01:03:05.829Z" }, + { url = "https://files.pythonhosted.org/packages/57/4a/a33cfa6dcb00e94194ae4fe710432ca4ed111e016356b4ba4d2f4c3a124c/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6774814fd5c338e72ee0da5cbb9432816df450e69c019f72b5d29bdec2a1792d", size = 1963437, upload-time = "2026-10-11T01:03:08.196Z" }, + { url = "https://files.pythonhosted.org/packages/f4/20/eacbecfea3b5c3dcbfc9b023e3a5460f43e5dda16d06a2877ebe7184c3f3/aiohttp-3.14.5-cp314-cp314t-win32.whl", hash = "sha256:33f706574e32c6e694f352a856e05caf18f7f2c871b3e87b41c55ea452b409ab", size = 503973, upload-time = "2026-10-11T01:03:10.481Z" }, + { url = "https://files.pythonhosted.org/packages/ff/78/18eec294f6c8c5dc845dcf6d730a0147d8d0f17e86138a7bdb85e43a30fa/aiohttp-3.14.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5ba14a839fbe87cf7c12a6b5661c05f324a296eb8363141edb3944ba63d4c9d3", size = 531190, upload-time = "2026-10-11T01:03:12.716Z" }, + { url = "https://files.pythonhosted.org/packages/9d/39/e53f8169acc85271ebd12b5b32ad7f1541b35639ccbe0f49034c64785d10/aiohttp-3.14.5-cp314-cp314t-win_arm64.whl", hash = "sha256:1061b364556e8172e8d46b0b183adeeb73e8c42d30ebc745591e1bd89acad52e", size = 510360, upload-time = "2026-10-11T01:03:15.08Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1c/06d89f58b2db3ee92dd377217659d587e06f973e57bf0a97a0d8a4586c0c/aiohttp-3.14.5-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:788ecaa9c10533b786ce5ba70c4f2df78ad41819fd00a6c99d92b66f9a32e1da", size = 540674, upload-time = "2026-10-11T01:03:17.483Z" }, + { url = "https://files.pythonhosted.org/packages/18/39/5e822e038f496f0540ada91e27099d48f9e7f919b6c6deb4cf6db36cc706/aiohttp-3.14.5-cp315-cp315-android_24_x86_64.whl", hash = "sha256:5c76f1802bab718a68ac3cce447160605c734551f95c67ae90fa1132b215cb29", size = 554183, upload-time = "2026-10-11T01:03:19.71Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ff/0cf2619d902b5b160762422a7e5e02091295766a7fe4fcf5b9a655e386c1/aiohttp-3.14.5-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:a6d02b4c38de03d9c7617813433e6a0fb6b522797974177d69d9dad431900833", size = 523160, upload-time = "2026-10-11T01:03:22.193Z" }, + { url = "https://files.pythonhosted.org/packages/86/99/3553abfc53a40849dbaacc3f54c730ec58410809ffa2d05885ae56108f2d/aiohttp-3.14.5-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:20726f9782d5c2744c1c66255842d1d163bb3edcf768b8de25216bf47f7b6ccf", size = 530311, upload-time = "2026-10-11T01:03:24.435Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a4/5d25f73754bc1e8f983ba704e86d641aea290c967f195f2aeebdaed2bd84/aiohttp-3.14.5-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:248d779ad720b49d4fb355720e60c9e5f444f95887bc16974fea48fc56c41789", size = 540274, upload-time = "2026-10-11T01:03:26.698Z" }, + { url = "https://files.pythonhosted.org/packages/29/a4/07eda5db2e3ee017d9590f36c12a94ec6f1f50516e8df78672373dfc7185/aiohttp-3.14.5-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:0a8ea271867e360ac985ae607f4a23ad9a38414b9aca1d49ec98839ae660e49f", size = 819239, upload-time = "2026-10-11T01:03:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/cb/aa/a8723dd987a696dd48d4cf2f0088e589ce77caebff0b96f2a78c20424380/aiohttp-3.14.5-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:823c910f046f23f4c713b8d99a2242dc65f591cb45ee86418fa11762a3c2963c", size = 549832, upload-time = "2026-10-11T01:03:32.02Z" }, + { url = "https://files.pythonhosted.org/packages/29/5c/969a1b72692055fd2a419590c41847ec9144fefb97b75ff1cde5b6372891/aiohttp-3.14.5-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9b42db919715e91eb76acf3bc492a9a7ccd8bd9adc6745c1412b689735269f14", size = 549234, upload-time = "2026-10-11T01:03:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/8e3e07fd8a0d33d06955ff4e54a1cb92f4bce347ff55e441dc3e25a7b5e5/aiohttp-3.14.5-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3112585250b199296c26ca6e0131640b6a8d01bab8b232d2eb3763ed469de11", size = 1891314, upload-time = "2026-10-11T01:03:36.899Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b3/05a79ce2e25f024e93de30c94f39dc6aa6e2bc1e9c531a5c4b18dc61b7a4/aiohttp-3.14.5-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c147451b4a58e7050f7f7394e6c467867c84161560001f9ad4fb2d1446743946", size = 1884372, upload-time = "2026-10-11T01:03:39.334Z" }, + { url = "https://files.pythonhosted.org/packages/94/52/0fd8af0717db109eea258191b326b5cb5847fb88928bfa6c862fd78b9ad3/aiohttp-3.14.5-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bf163cc701f3d4ac43ba7d97771bf5fd955220ef5500ef3ee847bc0ecfbf4ec1", size = 1930957, upload-time = "2026-10-11T01:03:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b3/fa78733da88812bf9fb193913fb0ce1548f8b6912047633fdf88a758ff8c/aiohttp-3.14.5-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d40ce41991e9d56fab4f5dc4a51fe59bc3b5c77c27f4b148963064d00232e8", size = 2038886, upload-time = "2026-10-11T01:03:44.646Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f5/2fcc5e30053a938286f17d0edf3f0850b8061b984256fa7c26850b9c8809/aiohttp-3.14.5-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:276a4fc00b1d9ae492b802763a789c5b86328b989c5ea169f2faa447d6a11c7c", size = 1894470, upload-time = "2026-10-11T01:03:47.304Z" }, + { url = "https://files.pythonhosted.org/packages/62/2a/f87feb42abe8e6a7c03814dbcb711540849a1e90aa392055f3183c643610/aiohttp-3.14.5-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50983e3be33d8c0942ab88cec3905b10602f64c469b20153c48c5d4e558dd016", size = 1716273, upload-time = "2026-10-11T01:03:50.121Z" }, + { url = "https://files.pythonhosted.org/packages/81/b2/adf1f960dd977722ed1347d33da512a1624807114f57a3b91f5cc828e081/aiohttp-3.14.5-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:16c8abd5bca220a47efe667d26f8460124c81810787e79ee87b242677563d9dd", size = 1857513, upload-time = "2026-10-11T01:03:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/ef8d910e641de4160026a513ace5888b7f92826bbc3fd90ced05d55a828e/aiohttp-3.14.5-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:0790ec66fa4013e83c53b9025a45d454723da1a2fce28b3208c9b32d08af162f", size = 1887704, upload-time = "2026-10-11T01:03:55.641Z" }, + { url = "https://files.pythonhosted.org/packages/7e/db/6c9142f941cba8d35be8e1fae6ea2bd390e754fc14076b8147aee1a4592f/aiohttp-3.14.5-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:cb11a971a3aea10f9b8373be628f1df932964fc6c6b174516d318a48c3ac4412", size = 1895684, upload-time = "2026-10-11T01:03:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7c/6a6bd9a72e576d376c668333b00c51c6147aba4fb863ed6c94996d497eca/aiohttp-3.14.5-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:932ce7e694bbc29b2bf6f64f2343c27d148d4997c771d01bdade4639b6749ff4", size = 1706299, upload-time = "2026-10-11T01:04:00.8Z" }, + { url = "https://files.pythonhosted.org/packages/69/ec/d2cc494242f8c4d3d1cd70baf591742818a74386dc3b84af3195c5c4fced/aiohttp-3.14.5-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:a7d470cf7b206e6359fc77b1b860632fde400d5a2ed59cd0181b93a686bc81ee", size = 1916645, upload-time = "2026-10-11T01:04:03.892Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6e/e852c53647e815db09a1b6b5ab634f54bd736412397e11940feef4d2c89a/aiohttp-3.14.5-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:6e1d8637cf73eebc92eba2e11d4cfff98a3b562f2505bd75bba766d908926e8d", size = 1875408, upload-time = "2026-10-11T01:04:06.922Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f6/72ab6ef20c332399be593bac543d2c24a6d241e39d3540ce9f95a63e4bd2/aiohttp-3.14.5-cp315-cp315-win32.whl", hash = "sha256:fbdc5ec49f9ca3cd24955cf3520b10a4d4c901ba2572094c84274e9e7eb30534", size = 485590, upload-time = "2026-10-11T01:04:09.626Z" }, + { url = "https://files.pythonhosted.org/packages/06/eb/e9de75b8c6d2170c42c08ff303abf857ea8a6d9d9b6e99b5aba40f15e962/aiohttp-3.14.5-cp315-cp315-win_amd64.whl", hash = "sha256:a9d3983bd6ab7aa1cfd573544ae98df9b6cb6912a5185a198263e024a636861d", size = 511740, upload-time = "2026-10-11T01:04:12.277Z" }, + { url = "https://files.pythonhosted.org/packages/fc/25/455f3c2785eb0d50748cffd0abd07500815f419a9495b14610b7622d8d2d/aiohttp-3.14.5-cp315-cp315-win_arm64.whl", hash = "sha256:e29347c142cf6e99e0dff5e2995ead1d50fa3b51bf37a7c726a7ccfe5419745a", size = 495171, upload-time = "2026-10-11T01:04:14.667Z" }, + { url = "https://files.pythonhosted.org/packages/e7/6c/497f0a98782eebfcf0f02a7fbdef5428148bd426027140cbad494cf842b5/aiohttp-3.14.5-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:9bf1d5dcc15204d9ec8b8ea4c18fd66e6b80e5de1f4ecbafb3a2f2740f8039d4", size = 855638, upload-time = "2026-10-11T01:04:17.154Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c5/55c0cef2572af9b1ee81608f7c0a1bf74e6c9151a73b04915d933f192335/aiohttp-3.14.5-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:14f04769cfefe4734016a856a83af36133cd17779cef9ae817f812b8ba9d6d51", size = 567455, upload-time = "2026-10-11T01:04:19.702Z" }, + { url = "https://files.pythonhosted.org/packages/4d/47/e1a0e39f4a2b881f6071225afaf94a6547001b5aefc1566734d73c685934/aiohttp-3.14.5-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:6e4251c0ba4624a68a2c11471a1ac54c3306876c21f0ae86de085cc9241c8905", size = 567836, upload-time = "2026-10-11T01:04:22.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1d/817d85836f52b687160064a326e62e037dc42f1ad5b6b5188a70e5c134b5/aiohttp-3.14.5-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4f5cf4dc72a71c4cfa9751b4950be22f733626670230d46e7d606592aa22d59", size = 2051409, upload-time = "2026-10-11T01:04:24.821Z" }, + { url = "https://files.pythonhosted.org/packages/f7/25/e8ea6fc212a9aabce982917346ce8ecab0929c74b60232a056dd11c99da0/aiohttp-3.14.5-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dbf53ae2601b7fd5a93c3944deea3a78d40f495226d582c35ef7a433425ce2b2", size = 1936493, upload-time = "2026-10-11T01:04:27.456Z" }, + { url = "https://files.pythonhosted.org/packages/24/33/de0517f71f1a19feea4aff78a2ec4ec2d98634129ec30ead78fce2f8f81b/aiohttp-3.14.5-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:657291433bf4dd3142f3abac495764cd47d0c7c92087751e6666c6447e65fcef", size = 2021405, upload-time = "2026-10-11T01:04:30.276Z" }, + { url = "https://files.pythonhosted.org/packages/c4/11/ddaf2e7930543e9f0cad3a1c54e1af0c1bd2fae3973d568c4263d3b010e9/aiohttp-3.14.5-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e51a27980c3788e6e6b3325d694fdd4898087fa8a86b2763af77b39353da41e", size = 2142017, upload-time = "2026-10-11T01:04:33.022Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7b/58784353c06de8adc20f426daad3d85dd86fd55331c713a3f4b638c94573/aiohttp-3.14.5-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82c7583cd3dfdc7dcc927835b4f6c7faae7ecc1ba3ca5879321621ae2e6f8e84", size = 2002482, upload-time = "2026-10-11T01:04:35.94Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f0/417d9535caa9e165ffe6a53e2347a78107e7c87dcb0e10aca315c3af0344/aiohttp-3.14.5-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6fdcd6af7e2e51d1ba1b4bea16e97b074bcb7b5dd0246a9d8201341bb28085a0", size = 1791774, upload-time = "2026-10-11T01:04:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/98/01/25e49c2e8a01b9f0e19ca0a8448ad50aa2bdf96c8cd41e92bd45af044784/aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:c8859a013ae0de1074660992139a1a440df3e6b219b86cf0d3f11c2692bb4fe3", size = 1970358, upload-time = "2026-10-11T01:04:41.66Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fc/c132fd3465b6c7e4ce0193154f602c3e6c46b680e4da7eb3bd0d8a40d1c7/aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:8966ecac808dd5f473c9c4cefd10cd3ffda71c18a4d3493b7c7d2ae1803bf2cc", size = 1969192, upload-time = "2026-10-11T01:04:44.66Z" }, + { url = "https://files.pythonhosted.org/packages/95/4e/d58b45e7dba4eb607eca11fc0a4aa77ae0f39c11afa804635a61ce18cff4/aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:3093b72c215bda16ce961a6d073f6e71d46e022962a9d5d457c5d4d421c78b57", size = 1963618, upload-time = "2026-10-11T01:04:47.505Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d9/f6ac50946efb3490428ef52b56e62c1c6b7f9c6ff3ec6083535526c83e60/aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:149fb56caf7acb67073126f675d0958d9c4b3125fcd3f6d4877df98aa8a97ce9", size = 1784817, upload-time = "2026-10-11T01:04:50.287Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2f/f255eb63da788cd8a452fe350869c03266ccad7d884925f6963c87808f24/aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:293d3ae7c6a0ed176a42e59a1b5fde825ead65c835360f734148e96729f928d2", size = 1995956, upload-time = "2026-10-11T01:04:53.213Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9b/241aa3393eaafda0034470add1625f82a4c252901108723b283a9b0b32ca/aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:3f2dcc00191fd563e9075181a14ec31d7dd63223ced7582cc70a15a499de0c79", size = 1967794, upload-time = "2026-10-11T01:04:55.958Z" }, + { url = "https://files.pythonhosted.org/packages/5f/7f/a68e689288c9e4bfcf8d0979f2b12b774bf01861985420df6150eba5448e/aiohttp-3.14.5-cp315-cp315t-win32.whl", hash = "sha256:7779cd97e61ebe583ec2f1c5616cdd038aa08a4453b1848c67842176d054948e", size = 503876, upload-time = "2026-10-11T01:04:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/a4/78/49b0299da6d54de19fc6fdc6889d50233ac47d192a461624f4fc010fde83/aiohttp-3.14.5-cp315-cp315t-win_amd64.whl", hash = "sha256:0e6f16f5e49c4b8267988c05ab07760d7064cea57d077c3d068d04b0fbb992cb", size = 530957, upload-time = "2026-10-11T01:05:02.23Z" }, + { url = "https://files.pythonhosted.org/packages/21/d4/b0afc936aeb6d2f93157e3408b069ec5d7934429ae7d023de5e0953b1887/aiohttp-3.14.5-cp315-cp315t-win_arm64.whl", hash = "sha256:1aead151c3abbac6b32942e452020cb66d7efc099d253cc6c20f748e926c858b", size = 510408, upload-time = "2026-10-11T01:05:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/68/30/173960c42b05a6c59f7558e4b12a4b0d9ba376cf6aa9bde7f9e08a30ca8d/aiohttp-3.14.5-py3-none-any.whl", hash = "sha256:efc21a454892828368b11c2c780de0ff8bc991f73f6b99c6b66e56205470929b", size = 279517, upload-time = "2026-10-11T01:05:08.523Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] [[package]] name = "backports-asyncio-runner" version = "1.2.0" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/backports-asyncio-runner/1.2.0/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "boto3" +version = "1.43.116" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/20/de5848b0166d8cb6b6e597b299ca10a64ebba3cf9aaab228de1e817ae85e/boto3-1.43.116.tar.gz", hash = "sha256:89fa15e7d1788fb17cbd694b034beba57f43f30f58f4506913a5b62220f8ba01", size = 112703, upload-time = "2026-10-20T19:34:57.874Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/de/cf71466f55516e61d8048374776b390a036a1b48c9a7a4cd8e21f8b27980/boto3-1.43.116-py3-none-any.whl", hash = "sha256:9dca4dbd464b189041b27b5a02280bd5e02c3051bebf6bd03cd0e47aaf558030", size = 140042, upload-time = "2026-10-20T19:34:55.932Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.116" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/98/aff05e8a35f5bb9f6648c6056ecff02a2bd4540ad70a89215db8a0cae758/botocore-1.43.116.tar.gz", hash = "sha256:00b1fbfc1c024b56633538385628a1bbc7ab15aca0b5b53e5fe188841d96c8ce", size = 16411854, upload-time = "2026-10-20T19:34:52.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/2e/449851ffdd5c6cd521d309bba501077025929ba0ef005a3052f43fca509e/botocore-1.43.116-py3-none-any.whl", hash = "sha256:92f1efe8434183f08f91a61478925f5f8d23082024556f269f916477040e26f4", size = 16111072, upload-time = "2026-10-20T19:34:49.566Z" }, +] + +[[package]] +name = "certifi" +version = "2026.10.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/d2/2cde336b375f55c76ca670f0be3978cc048e31e24f3b4d7ce8473150a388/cffi-2.1.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be", size = 183779, upload-time = "2026-08-03T21:19:15.602Z" }, + { url = "https://files.pythonhosted.org/packages/94/1a/4b2f7c92293ba05cbd4a9a1b28faaf0326272d9488e6354657571c48a7aa/cffi-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b", size = 184178, upload-time = "2026-08-03T21:19:16.67Z" }, + { url = "https://files.pythonhosted.org/packages/17/0b/ba385d8ccedf926c3cd06e8e2f327027da5afe5f0eb30f1f7bc43ac55125/cffi-2.1.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004", size = 211037, upload-time = "2026-08-03T21:19:17.705Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b9/0f2e58b2cefa33255bff36935d42b13180fe559bba82596540eb404bde7d/cffi-2.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9", size = 218652, upload-time = "2026-08-03T21:19:18.735Z" }, + { url = "https://files.pythonhosted.org/packages/37/15/180e0dab27b9312c7479003d14c9e547634b7dcb934e2cc4650e1b131a7a/cffi-2.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98", size = 205422, upload-time = "2026-08-03T21:19:19.96Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/03026f0c850cbbaa9030750490225b4a7f4d524ea4df72c3cc740a90f4ef/cffi-2.1.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9", size = 205444, upload-time = "2026-08-03T21:19:21.246Z" }, + { url = "https://files.pythonhosted.org/packages/75/77/60bebf6f818bec84210ac5b6979ce4eeadce6fbbaabc9c7ab23e506d1ce5/cffi-2.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6", size = 218742, upload-time = "2026-08-03T21:19:22.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ae/679bf47e73fd77b352171727f07de559a003f14de5d02b904a6ec1fa73ca/cffi-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf", size = 221054, upload-time = "2026-08-03T21:19:23.694Z" }, + { url = "https://files.pythonhosted.org/packages/09/b8/eefc0e06913b70aa153bf74c946094a18f58fd4aff11b7f372bfdfdca050/cffi-2.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659", size = 213489, upload-time = "2026-08-03T21:19:24.922Z" }, + { url = "https://files.pythonhosted.org/packages/6f/13/4e56852824a03cdf68523a35686f1c28eacd4bd30a7b0a78e682e6e6e1d3/cffi-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9", size = 220241, upload-time = "2026-08-03T21:19:26.214Z" }, + { url = "https://files.pythonhosted.org/packages/99/7f/040f9e163e4acac3ee3d85b02d00b2576e7ca980d8785f0a3a5f1a9bf7f5/cffi-2.1.1-cp310-cp310-win32.whl", hash = "sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41", size = 174578, upload-time = "2026-08-03T21:19:27.338Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0b/644a2ec1a4eaba49c2939410bb1eb1d25b09d6d0582f5d2f95c537043725/cffi-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1", size = 185082, upload-time = "2026-08-03T21:19:28.409Z" }, + { url = "https://files.pythonhosted.org/packages/70/d2/16d99a0c4948febc0ebd133a13b2f688ff7f8cb04da971e1128872ce0c03/cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12", size = 183838, upload-time = "2026-08-03T21:19:29.637Z" }, + { url = "https://files.pythonhosted.org/packages/cd/95/31b535a9f0220ae9f357de4a08d57ce89cb417653c2fd9f075f50822a388/cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1", size = 184168, upload-time = "2026-08-03T21:19:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805, upload-time = "2026-08-03T21:19:31.867Z" }, + { url = "https://files.pythonhosted.org/packages/ad/66/c19feabb28485b6e0bbaaafa90837a1ef5d302e90f2178bd33f17a49879b/cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813", size = 218716, upload-time = "2026-08-03T21:19:32.896Z" }, + { url = "https://files.pythonhosted.org/packages/a7/92/500760486c8baab49a7a8a58ba7fc3355ec3974b454b8a09e528efde9e1d/cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990", size = 205569, upload-time = "2026-08-03T21:19:34.142Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a7/a67c733254d6e7373f7822f8082d8d6beade791e0cf12a7611f376fa61c7/cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af", size = 204907, upload-time = "2026-08-03T21:19:35.174Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a4/4399daaf8f7dfee9d7c3327fdb0426ee041cc63edc358b93911ceb2bfc7a/cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632", size = 217807, upload-time = "2026-08-03T21:19:36.286Z" }, + { url = "https://files.pythonhosted.org/packages/28/f7/dabe6da2466ecbd82dc62e7342dc6b1065dad990c06f00f0ede9ebf2a0ed/cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd", size = 221252, upload-time = "2026-08-03T21:19:37.416Z" }, + { url = "https://files.pythonhosted.org/packages/ce/87/616202d8e51342c07d2534c510111c4cc37201775ce8f60802c9335d1edd/cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a", size = 214214, upload-time = "2026-08-03T21:19:38.507Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c6/ab025d75d2c26c19b087c0124e75ee31cb65032f4fe345d356d8c507ab97/cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa", size = 219408, upload-time = "2026-08-03T21:19:39.809Z" }, + { url = "https://files.pythonhosted.org/packages/db/e2/7e8109f65445bdc673a7b54f02c677de462db75674220fd1335efc8eb598/cffi-2.1.1-cp311-cp311-win32.whl", hash = "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3", size = 174470, upload-time = "2026-08-03T21:19:41.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/c0/77ba02423c2f7d7091143c45cd49e0e6575c4c1967394bb542bd923a9b74/cffi-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0", size = 185096, upload-time = "2026-08-03T21:19:42.615Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/9f1f85f9672ceda4984dc6c4f8824e8558992a2972c3d3c81fb8eb28d4ba/cffi-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455", size = 179941, upload-time = "2026-08-03T21:19:43.747Z" }, + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/1c/f41d4e74c28ab327ff3acd36053f7ea506c55872d7a90b0fa71aa3ab0c89/charset_normalizer-3.5.2.tar.gz", hash = "sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef", size = 172659, upload-time = "2026-09-30T04:39:23.398Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/backports-asyncio-runner/1.2.0/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5" }, + { url = "https://files.pythonhosted.org/packages/98/77/46e87bcfc45d25ab4db7cfc9bb544bfa3ffd302289ed31ae93f5433eb899/charset_normalizer-3.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:195c26fb65950f8fce54e26349852b7bdd7c5f120aeefbcc440b8a20faaed4a3", size = 373348, upload-time = "2026-09-30T04:34:42.843Z" }, + { url = "https://files.pythonhosted.org/packages/52/fc/e518013affcc43c9f919c3ba41bffe9b4ee4aceb0a6462a6243efcca5f2d/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9373ad13ef0d2c0fb761e04e55bfdee5a08b52cef2c882c8fbe9935b1517152e", size = 260860, upload-time = "2026-09-30T04:34:44.817Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ad/2a895c945ee61988dfd9ccee64f0b78dc09f29c9b34d1dd545246d78e0ad/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ddf19c062bea7a0cc80f519243d2c01dd091be0cf952a0750d4ad576709559f5", size = 246491, upload-time = "2026-09-30T04:34:46.227Z" }, + { url = "https://files.pythonhosted.org/packages/c9/05/5d958bc8ea503e26be25ada5430fd409cfb45dc22ff33f9f96e649613c99/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d14b50de6bf4d0edf857a9386836846f982b8f524e188e2e68b96d702bcf4aa", size = 290875, upload-time = "2026-09-30T04:34:47.691Z" }, + { url = "https://files.pythonhosted.org/packages/55/e2/06bad57dfdb49cad92c0ba85b6b4fa5827a67df37897287cfef0553843c2/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:28a15fdad492a99b6eccfaaed66ef3f74050680545ea61ec8b2f4c538f1f1320", size = 286987, upload-time = "2026-09-30T04:34:49.356Z" }, + { url = "https://files.pythonhosted.org/packages/c4/9f/9f52d2886d52645987d603425482c63c5045a3005db1354a7097e5ed1ae9/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a893cc101149f80a653f82062ebc95b34525a2614382e1da5458fe7c6997249", size = 271105, upload-time = "2026-09-30T04:34:50.957Z" }, + { url = "https://files.pythonhosted.org/packages/4a/41/e05e19578b7b87e7db2c3ecb884bd09d065274ab41e1f535542e8bae9b06/charset_normalizer-3.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:619799369eeef6366ed3e8755a5670f4f2f0fb6b30a0fd7264dc0fdc2357058e", size = 269833, upload-time = "2026-09-30T04:34:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/fc7152414561ff78f9c61a4a627025f65fd244abaefc99ac4e75c6169b33/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:447441e76ec720b15e64418d32e092297340387053047c7c694f579efb0ee1d9", size = 263048, upload-time = "2026-09-30T04:34:54.013Z" }, + { url = "https://files.pythonhosted.org/packages/71/71/fb379e399b1013962716a059d551e03bd80b3e02f05d81246886af6c0958/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:62588a277bfb59def052abd940703fa35107152bf479781a878617d60faf8fb5", size = 249237, upload-time = "2026-09-30T04:34:55.373Z" }, + { url = "https://files.pythonhosted.org/packages/33/4f/aeadccd6d20882909eb2597ec40eccc05d505e2262f14dc76d7620700657/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:44bd4fbb29dfbeba60e7d2bd000c59e4b21ddb3cc53912b14048d37092706d7c", size = 290756, upload-time = "2026-09-30T04:34:56.867Z" }, + { url = "https://files.pythonhosted.org/packages/b9/7b/e8a92613236b257d3b75d532496eb21f68ad8231d7d56df94497e460112e/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:30fcd120b732aa79317f08dee04d7de0847822e4cf7ee0e9f445bb958832252c", size = 269026, upload-time = "2026-09-30T04:34:58.278Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ac/21d5c6b972285c5f095ff78afdc99f3539e3985dfcf7ff1cfbe9e772f529/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:50e3adfb96fc189eb27b1cf62d3b598b89b4bb0420d93a3d3e42e137409011be", size = 287601, upload-time = "2026-09-30T04:34:59.703Z" }, + { url = "https://files.pythonhosted.org/packages/59/72/263491ec1494a194b16fcbff88a0220f2af633738c79e92b4d7189226322/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b736353c0a625bbd5fcec108576e2385db3496f4f771f785ff32e108d3c3bc45", size = 272660, upload-time = "2026-09-30T04:35:01.26Z" }, + { url = "https://files.pythonhosted.org/packages/38/7a/81f1186c57e406233969ae96b5fd0c907254ab8b6e47da8ae24cd20fb9d7/charset_normalizer-3.5.2-cp310-cp310-win32.whl", hash = "sha256:f5833ad231be5eb6553de524a70f48d71b2c8563101750531e0b80184e175cd4", size = 187301, upload-time = "2026-09-30T04:35:02.659Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ab/fb8b5178fdf7bf68efa61cbe892286bc9f73846f26147d4725fb89388475/charset_normalizer-3.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:1461ac396c4fdb983a675f20aa555624f0ee18ac83d832b9244ffff3d8055275", size = 215446, upload-time = "2026-09-30T04:35:04.011Z" }, + { url = "https://files.pythonhosted.org/packages/36/40/e270b74e0d3f583d78d902960845a149e80eb7c737ee5613ccc6efc7264b/charset_normalizer-3.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:c6708715abcf3c73b99508253e961a9967f02fe536532834149574eda6de0d1c", size = 203389, upload-time = "2026-09-30T04:35:05.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/67/6a0b94a7960d5e1b5eacd2fb529f3fccc47db4644f7f0a7cfdcfc3be578a/charset_normalizer-3.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6", size = 370642, upload-time = "2026-09-30T04:35:06.91Z" }, + { url = "https://files.pythonhosted.org/packages/fb/94/01009e13b94041599004edf32e56e382c24e570f60f79bab8efe45cfe1eb/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d760fe2a4d7c3b226cb9026d6a842868d52a7901bd98420e1baf14e80da85cf5", size = 259280, upload-time = "2026-09-30T04:35:08.448Z" }, + { url = "https://files.pythonhosted.org/packages/66/85/3b5358f60a13210f0b67d3755c168ef758701b021e655d88d4da28554467/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c9790464842f85f437dbbb54417eda1e0e6bfc52dd8d22d6fd1c994b73b2dc74", size = 245335, upload-time = "2026-09-30T04:35:10.104Z" }, + { url = "https://files.pythonhosted.org/packages/74/75/77c1c479b09ecd751d1e767b251ea5c14d4d50ff757bf404afab2692f600/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4685902cf26edf013ed7a3da0f426ebba7a00ebb9541386d835afbf002c11cab", size = 291707, upload-time = "2026-09-30T04:35:11.575Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0d/363f78cacb70f58f15f4b083961bbd9d292f335d3f5c66fc4f1cfe69cb90/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4495c5002a7b28557e7e222e77e0b661183e432b7d6d2e788101e3f240e05b8c", size = 286747, upload-time = "2026-09-30T04:35:13.022Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ed/cf505d3011ffceb12c2067a7a5d3cfe92b875d4d44bb0ff0d69375e2c184/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:211d5a3eb6af8f513b8d4ca19a8c1b7accab1b5f0d3175f9826b03c1a920dc1f", size = 269972, upload-time = "2026-09-30T04:35:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/15/d8/f0a93a431d170e7ca681d4f6650fee3de934d18560e474e7267eb4b0f987/charset_normalizer-3.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef4fcbf3327382cd4c9f540babd61248208af7b93eec4de397b4d5f58a09e288", size = 269339, upload-time = "2026-09-30T04:35:16.087Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/9b2bd1c5b7af02462c9752d33994834ff972a96b4c483eefde9e594488e2/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd16aabe4a02a297c23417aa17ac6299dbd8c49f673bcd645b4929b11f5a4400", size = 261683, upload-time = "2026-09-30T04:35:17.488Z" }, + { url = "https://files.pythonhosted.org/packages/76/a5/cac540ab0fd61f3fec88ad3dbb64509e71424593d73cfdfff5ab3e4db279/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:fb9e68df06293761f9fe66ade60a9bc6d0f5e42b8acf2939a9158af86ab0e5bd", size = 248143, upload-time = "2026-09-30T04:35:18.849Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/ff467301deef2089fad87f72df9e000a26a78fec7acbb18e1999371b8369/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:59f63901b0031c3136cf64704dcb21de0bbae62ce2c9529bc39d27665463de37", size = 292358, upload-time = "2026-09-30T04:35:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/ad/77/22d7e785d1e210afc2e2f58600dd1799d17a35665faf84383f002826c5f8/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:304d5463e65a35d7bb0850550e0780395395f6fcf452f04db7d5ca7cecc425ac", size = 268766, upload-time = "2026-09-30T04:35:21.72Z" }, + { url = "https://files.pythonhosted.org/packages/ae/91/e8e946267f1c2d9e2bd651726e2fbd2addf02c4d36cea5069e32ca9d7bb5/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9cf9b1a857e25c4baceeb3624e92a56df3668f398c4acba74e174d81fb4d1d3a", size = 287432, upload-time = "2026-09-30T04:35:23.273Z" }, + { url = "https://files.pythonhosted.org/packages/4e/88/7561d8a88d555e7df6623abe7c0070b4baf47549b9408783a2ae0a1a6cf7/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:114e4d0c92d618409ed82a99e22b5c5e768fe995f2973f78265f4524f49d4640", size = 272470, upload-time = "2026-09-30T04:35:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/35/7e/578c702301ec036f01455f30744a08d2b42f6ab35b9b2d4bf8cae0ef2a80/charset_normalizer-3.5.2-cp311-cp311-win32.whl", hash = "sha256:2625388c6c754520c37abaf3b41eb34d1cc4a373f457898f08606c8e362b891d", size = 186688, upload-time = "2026-09-30T04:35:26.225Z" }, + { url = "https://files.pythonhosted.org/packages/e8/fc/fdf8cf52ff21cd5bf158f20978991cf985325842f74283eb6df26c8a39d8/charset_normalizer-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:87e50a3e7cb90af586b6c5faf23e302a970415ac73bd7bd90a515a04b427ef96", size = 214932, upload-time = "2026-09-30T04:35:27.796Z" }, + { url = "https://files.pythonhosted.org/packages/97/66/3e45a506d8110b632541faf9a9470185aa9878f1ed44020f31346c1c5e5b/charset_normalizer-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:254eb48b9fa5ee9898a3c445825a1f340fe53712a098904b39b0bddba8ea3cb1", size = 202828, upload-time = "2026-09-30T04:35:29.259Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c8/693809898870237d82785a03f3b2b58fe4c9f14669f84a7d4e623c92a59e/charset_normalizer-3.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed2a239c0ea213acc1908150a3037257083c7c083128f1a4cec2ec4b97dca491", size = 367780, upload-time = "2026-09-30T04:35:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/c9/87/2fea8c13dc24b3ca9c6f803a5b2dfdeae73eb4f9e12c7885ed908ff0433c/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b91363207bd9dc966a691e959bb47f64b30f7ac4b072be9968b366982f7db77c", size = 246730, upload-time = "2026-09-30T04:35:32.286Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9e/09efac30b937722f46d3110ba30b875b24b2e3a266ed746cc4e376a94d80/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:38a873987f3be698494da8b2e3085e29da02da7b633dce73e79c699a113d7bf0", size = 237707, upload-time = "2026-09-30T04:35:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/9e/18/70d76670b13686237863a379928d60bd10e021f17d243ab3d7014c4a5f4e/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:355ad8011081dec5412240c087a9a0c9d4d5039f3ed11a3f13e18c2b29b56c51", size = 273050, upload-time = "2026-09-30T04:35:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/54/e2/77a8b09d5adc013ed07b95b01b8b8fa5441c4e810e83ee7e4aae2fa4d91a/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ee21e28f0430bd6dc9086c6e525d5e818a44a5ad19720c8a0ef766792f3eb5e5", size = 270345, upload-time = "2026-09-30T04:35:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c5/38806a25ab5e65fc178f39affeda20858efafede2fce1ffc2556cfc9fe73/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649", size = 257601, upload-time = "2026-09-30T04:35:37.919Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8d/213565184708fdb263ae55e2c04ee1ff748129dd65d48ed0e3502da9c85a/charset_normalizer-3.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5cde776b7cc66e4f6c99612cea4aa7269aa65863f7a15841b2c264f103822f4e", size = 252222, upload-time = "2026-09-30T04:35:39.544Z" }, + { url = "https://files.pythonhosted.org/packages/7e/24/76d2cefc25472531e4c5c7dfff68865eb1c39b78482f0fdc15b46f047830/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae4f5fea5b8b8ccff88238cc8569303e5ee95efae67fa62922a311397a71f346", size = 248482, upload-time = "2026-09-30T04:35:41.088Z" }, + { url = "https://files.pythonhosted.org/packages/7d/dc/65a801b66ab4c197e22c433ab25e7ac24324ac6f45a2269aca42cce309bf/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f7d486c83842422badd511868fd8a9a20e9407ace71564b6af47ce7e60a336c1", size = 241206, upload-time = "2026-09-30T04:35:42.59Z" }, + { url = "https://files.pythonhosted.org/packages/a7/95/ca9b5eabde673002c6f1e7ada1b223916fe18f6d661da7aabd4d643718f1/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11a4d68a6ecda3292cb1e50239e111543ba5d709bb62a6b4ea1afcfa729d8875", size = 273190, upload-time = "2026-09-30T04:35:44.347Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8b/803b4d2a3f6e1740f63f1e87b04d14b42f3d4fdfe6ed7d4db2d34102b14f/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d6734d2ef8a50fbf8445c139477da401f50d62a0606bf00e20ec6d87773fefb1", size = 253527, upload-time = "2026-09-30T04:35:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/a9/55/93c0e5dbd085ae0471346026abbe7e0db9ea2d6fea74e51f0b5a46f233a7/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a815775b6c38d4e0ff7bcffbeba67feded90202bb6a226b8dd35f1c855217413", size = 271285, upload-time = "2026-09-30T04:35:47.49Z" }, + { url = "https://files.pythonhosted.org/packages/95/69/0dbd0e0b9b16cfa816cdfcb3e2e3854a1f680dc07fb1245ea125e7448060/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23851fb4e1b85ed3f6c2a27b777cdfe2e19fb5b38429a8faf38c7542b7665869", size = 260010, upload-time = "2026-09-30T04:35:48.996Z" }, + { url = "https://files.pythonhosted.org/packages/58/9d/e7b88e7b1bf403590c3b573277b5e1e488c68c7a6fbacca310a2c324e90c/charset_normalizer-3.5.2-cp312-cp312-win32.whl", hash = "sha256:db19d07e2e0129e974a0e65d0064fc222a446cd5122c2fd4184d2af9fc734a9e", size = 184125, upload-time = "2026-09-30T04:35:50.777Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e6/e6e083884cbcfd49c64865af05027fe7011be7b2d9179524f099a1b611f3/charset_normalizer-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:780fbe7cab297b81dad9fb8dc5eb003c0468ffb0d9e5f65068c53a34661a96bc", size = 207486, upload-time = "2026-09-30T04:35:52.194Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e3/017aea0911ada7405a825c7d937eb3a13009664e2f5b38e8c4bbf2abf894/charset_normalizer-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:e2af3aad578aa6bd1384bcf4750fc285e5a9de53f40b7d41e5a0bf748edeb2b3", size = 196734, upload-time = "2026-09-30T04:35:53.636Z" }, + { url = "https://files.pythonhosted.org/packages/c5/34/68292d68512768591aaff07c59bb53ee31341c87759433a859c4641a50c2/charset_normalizer-3.5.2-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:ed905975ab14056a2e5eb1c376cb2e1ebc5396baf84163939c518556fccde9f5", size = 225946, upload-time = "2026-09-30T04:35:55.313Z" }, + { url = "https://files.pythonhosted.org/packages/e3/80/bee0b01b90ccd5322ae1d0abb33fab1bd95b7c2eadaf02aeccf22e04ee83/charset_normalizer-3.5.2-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a66c3bc5ab1f0ff2164fc9965ddd611ff0802173f4b9d24554c563f6ab7e1d6e", size = 238619, upload-time = "2026-09-30T04:35:56.863Z" }, + { url = "https://files.pythonhosted.org/packages/78/6e/60ce52a85a7fd631ae8482ae6d74521014ca2f255892679484dc04d7ef56/charset_normalizer-3.5.2-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2374b62878abb00cd8309b32af6c0b715cd02dec0ca74ef12e5069bdc64144a", size = 206701, upload-time = "2026-09-30T04:35:58.639Z" }, + { url = "https://files.pythonhosted.org/packages/36/8c/71aafad23f971afc84c2b295bc0c560739ce1dac558aad9fec22e39f3639/charset_normalizer-3.5.2-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d376bbd28b3a8999db1a103b3b388aee6f1ddeb3e51bc2172993efdcd86e064d", size = 210506, upload-time = "2026-09-30T04:36:00.147Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/3c5a7798c046df7d2d68ad653cf5b6c5a8bfee225055a843c6f2f42aac1a/charset_normalizer-3.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6045373d5a89a5ec71afde535db987ca28e76dfa276c2d4c818265b375d4b055", size = 366626, upload-time = "2026-09-30T04:36:01.77Z" }, + { url = "https://files.pythonhosted.org/packages/e1/16/710ac3de2ee354e2bd1a9c94efe45a2d27b5c6ad39b2d6a905be2c094b6c/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:849df64e889b2e17230d58410a03dba311a65b163508fd33679b2b737d4b7858", size = 244624, upload-time = "2026-09-30T04:36:03.389Z" }, + { url = "https://files.pythonhosted.org/packages/d6/39/45c7439f5b63d24f7d5b2a1d760f34af7628782d7144b4cc8ded45c2d4bc/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:15c44f7edfd477b06f517a5cc317fc1707edb9de2c865f43d4b6513907473234", size = 235731, upload-time = "2026-09-30T04:36:04.987Z" }, + { url = "https://files.pythonhosted.org/packages/4d/34/38f3154785ce92e9f56eb226f4d35bdfae6b008480dd055f58837a89c810/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a89012d6d5476ee112d20d998570ed58df2260a852afb1758809cd6900411d21", size = 270352, upload-time = "2026-09-30T04:36:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/04/f3/859f74e7babc977705026b30593b3be04049632a522fb7000f83c033d747/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0c951d5e6dd9c2ff60609476752bee49da4206adde960ebc247766937f72e718", size = 267393, upload-time = "2026-09-30T04:36:07.865Z" }, + { url = "https://files.pythonhosted.org/packages/4b/85/41d27f234b82e47c167a5f6c0f62501dc0c640585ff4aba79e08a390336a/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7218e8f32b0956cfcd048fd42d9d5779809745ca1d86113ca56f66e7ae1549c4", size = 254660, upload-time = "2026-09-30T04:36:09.248Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/5d1a997587febe5b26d8daffe363b5c1a091cece19828eec6502fd09c5ef/charset_normalizer-3.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19a731138fc27d5682277d3b9df22855cea1239bce7fcec5f78f42ef2d1f3c3", size = 249996, upload-time = "2026-09-30T04:36:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1f/d1e78246f7ed60c8c8d606b4ac27f66ce49cc3e95f24893ccbeba9f77302/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62603db9a7caa0802eaa28c1c46fecd7b3a263a774069c24c3c28c302448721c", size = 247028, upload-time = "2026-09-30T04:36:12.294Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/eba316edd4f0c4d3a5d945924c4eeeae59abac4056aa815d8a4268f863a2/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b6856554c4f44d79fc2307d5768854310a8f0096e501c75637542c82292b0429", size = 240562, upload-time = "2026-09-30T04:36:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8e/aaa037d40ca9ef045977f1a661048b1aa33f223adfce3452fe9be9f79d14/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1bc0baf5ef96b6ede57d47f4b8fe4d9d84019c3bfcbeb20a41edc6a6ee341f1f", size = 271699, upload-time = "2026-09-30T04:36:15.41Z" }, + { url = "https://files.pythonhosted.org/packages/26/19/1c1c9f75974adf523b87f34b8a2adc5a435cd65916812bcbd0dfa45f9a29/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:56bc200a365efb37383b7852e4cc5898d3b2da5987289b543956cf8cad71018a", size = 252030, upload-time = "2026-09-30T04:36:16.839Z" }, + { url = "https://files.pythonhosted.org/packages/bc/90/0660ef18e18df0a4d2a1a0edff7dfbba42d4e50ef2425557a5bb7051f77b/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2c9ad19a6cfcd5ea5c0d41161d22f9df1dcc277e9bef2751391334546a314c00", size = 268565, upload-time = "2026-09-30T04:36:18.468Z" }, + { url = "https://files.pythonhosted.org/packages/79/ba/57adc269824e8658f1a0f97a9e514c247445a9632b3419b97e0ba37f16dc/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e243bd13217235fc7290c621941c3f5cc8b66e4872495be821d7436ba2fb838d", size = 257627, upload-time = "2026-09-30T04:36:19.938Z" }, + { url = "https://files.pythonhosted.org/packages/9a/85/33abd4315c052d3d4f54c92b1ee49bfbc0dc7115a981e462a793b6d2ab87/charset_normalizer-3.5.2-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:a090bb2c68df85450502e3e20d665e3a5af9c65a84d6508ed477badd49166fd3", size = 143407, upload-time = "2026-09-30T04:36:21.376Z" }, + { url = "https://files.pythonhosted.org/packages/4f/de/6435e18d1aaa5d910b896d551411c96af1f42a0c56c29afc2016c61ccc2e/charset_normalizer-3.5.2-cp313-cp313-win32.whl", hash = "sha256:2b7b3bbfb4fe8ef40600792d762fbaa9057559f9d3fad209525b7a22b99e91fd", size = 183207, upload-time = "2026-09-30T04:36:22.776Z" }, + { url = "https://files.pythonhosted.org/packages/9c/76/b8ec57f4e9ee3253541abf95e4a462c0175fe8032dcd070f1f2421240942/charset_normalizer-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:78456a747de8dc58360ffa581f30a002baf5aa28cb262536545e91f113ed7639", size = 206489, upload-time = "2026-09-30T04:36:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/c647c6ae47480221e875ea5d743ff94946f7416e3c69415ab772928e8d32/charset_normalizer-3.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:11912e4bb14baae7c5d8791aa55ba0a3a03ec6729073307b0f57270abaa713d3", size = 195501, upload-time = "2026-09-30T04:36:25.846Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/7aa91362a2f77ac8e9e28a9b902a74f7d0e11a851ef0d27a74308da8cd90/charset_normalizer-3.5.2-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187", size = 226010, upload-time = "2026-09-30T04:36:27.669Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cf/ac8878d0322cf88a1aad4c7b147db32ca0bd806eb0060957b2e31486dbe6/charset_normalizer-3.5.2-cp314-cp314-android_24_x86_64.whl", hash = "sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad", size = 239266, upload-time = "2026-09-30T04:36:29.434Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6d/9a08d7e0b29b7208e2c6c01dc56c8e0520e7c7beadbbfb024b58fd69c8a5/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf", size = 206783, upload-time = "2026-09-30T04:36:30.872Z" }, + { url = "https://files.pythonhosted.org/packages/82/44/b0aa350280e6ff5a5492d17cf10460dd39d5ee848f872f7ba2df10607f60/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995", size = 210449, upload-time = "2026-09-30T04:36:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/40db9aa9f5907bb0e6f8b6d64064bf8852fb33d4b813ff9414911df7647c/charset_normalizer-3.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424", size = 367815, upload-time = "2026-09-30T04:36:34.197Z" }, + { url = "https://files.pythonhosted.org/packages/7f/72/9c5e7707b57c8ddfa9ddf7b0b1d009d7fbab9e9e887d5b721060f37e307d/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13", size = 247095, upload-time = "2026-09-30T04:36:35.803Z" }, + { url = "https://files.pythonhosted.org/packages/83/09/71e453691e927de4ddf792770cfaab3f49d494e222f66ea5e404bbd5e39c/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d", size = 233942, upload-time = "2026-09-30T04:36:37.407Z" }, + { url = "https://files.pythonhosted.org/packages/9f/86/85c84e4da8b27dd409577d9437926ff581c5f9d3c66038dc68c1a526de51/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4", size = 273762, upload-time = "2026-09-30T04:36:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/564955a4b5f2ccb410ab480bbe8c6a18063ff27f2d35458731c4a5335df9/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438", size = 269514, upload-time = "2026-09-30T04:36:40.469Z" }, + { url = "https://files.pythonhosted.org/packages/18/24/bad3ac4271589df29cf5ce2f5ae490518a5739358052bd0d61209e6fea54/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a", size = 255056, upload-time = "2026-09-30T04:36:42.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3e/350d89ad49916b86554d6f5f2d03ec1152148f87e5ff735106c6a03b1a36/charset_normalizer-3.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56", size = 251311, upload-time = "2026-09-30T04:36:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/56/5b/4970a2d154df502e133402906dd04e3ae7cada7b3011283c88d0479a2585/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd", size = 250190, upload-time = "2026-09-30T04:36:45.185Z" }, + { url = "https://files.pythonhosted.org/packages/88/8c/f1a91bddc8fb47c2889e29ea7ea49a194eb0d9868675d786806519c00d76/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204", size = 237098, upload-time = "2026-09-30T04:36:46.689Z" }, + { url = "https://files.pythonhosted.org/packages/24/0e/bb5dace3cc7e79068425386a6589c19b5a2ab5fefc2a46abea6919683332/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7", size = 274228, upload-time = "2026-09-30T04:36:48.31Z" }, + { url = "https://files.pythonhosted.org/packages/9d/79/b849ad523017ea9f5a45581bbebed91439e0cf42fd2860a6f64e358eb5a6/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd", size = 252401, upload-time = "2026-09-30T04:36:50.091Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/75469d690cf47200bce8f6cad7655724fc23148e147abfc5ce78b5f65863/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc", size = 271846, upload-time = "2026-09-30T04:36:51.719Z" }, + { url = "https://files.pythonhosted.org/packages/26/cd/6d52d3c7437cdcf2e310ce9f28f282e733d4ef60ed19105d1819c356255f/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874", size = 258151, upload-time = "2026-09-30T04:36:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4c/070b38bdb5f49a70199fce923ec0726a49536a63ab262abbfcaaf351110b/charset_normalizer-3.5.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655", size = 143570, upload-time = "2026-09-30T04:36:54.816Z" }, + { url = "https://files.pythonhosted.org/packages/81/84/9ebfc8ed6c8c4fcd8e726ff6bf220cc8deb3966e31dce9be8dd8aa017e64/charset_normalizer-3.5.2-cp314-cp314-win32.whl", hash = "sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0", size = 186412, upload-time = "2026-09-30T04:36:56.643Z" }, + { url = "https://files.pythonhosted.org/packages/d1/78/5ed86f743d4bc350db307e7636419a0a5ee1d91806d30c7f667bd5c80dae/charset_normalizer-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c", size = 210965, upload-time = "2026-09-30T04:36:58.205Z" }, + { url = "https://files.pythonhosted.org/packages/53/94/a3a7698e9b1a395e1eb99ccd9a324be9347973bff4e72db2a06496d7cd27/charset_normalizer-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253", size = 200406, upload-time = "2026-09-30T04:36:59.764Z" }, + { url = "https://files.pythonhosted.org/packages/c1/48/c5dd00d5ef7791f02666de250a5bb6071e29b7e133cf4b835800b6d3bc27/charset_normalizer-3.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709", size = 396871, upload-time = "2026-09-30T04:37:01.543Z" }, + { url = "https://files.pythonhosted.org/packages/12/c8/8379554b42e8368161d898476686947a0fdbd3e8865170d7909dcabfdee8/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084", size = 249711, upload-time = "2026-09-30T04:37:03.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/eb/2ddb1035d17320caa9f41682935123a9a250277b261c3efc86b2d2a21343/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb", size = 231146, upload-time = "2026-09-30T04:37:04.721Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/2ecb4bde104322cd7859d6594fcfa74649f8d90b3221c9feecbef149875b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f", size = 271552, upload-time = "2026-09-30T04:37:06.295Z" }, + { url = "https://files.pythonhosted.org/packages/3f/98/9d5f6ebc3aee9fef5d30b4aff11fb2ab7a1222b4064f8ef2c7c87cde217a/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09", size = 269810, upload-time = "2026-09-30T04:37:07.905Z" }, + { url = "https://files.pythonhosted.org/packages/09/e1/a3b06a10461b1b7628853c934c644e03bc28e42767116afb52f19a56519b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80", size = 259845, upload-time = "2026-09-30T04:37:09.554Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/6f561f74a296cf27d61775a1dc665ad13f3bff6a798810ca05907f37a7c4/charset_normalizer-3.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c", size = 252825, upload-time = "2026-09-30T04:37:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/69e13ca3b18f43e0eafcd34c04a45b732ae22a43b54a5fc9e119103356eb/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f", size = 252659, upload-time = "2026-09-30T04:37:12.941Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/ace29806a0dae18939919c76ba526472d83214afa101105fabff2cf30625/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03", size = 238819, upload-time = "2026-09-30T04:37:14.659Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c1/6116d52a2e3311ec80f21f5fb5e17b27405f10b9608af8f6e69516841a1b/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604", size = 272973, upload-time = "2026-09-30T04:37:16.346Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/9955c7e93bba10a9c7e8f7a5031b7ced66f3a1883a55c00712b8d5850ff3/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8", size = 253692, upload-time = "2026-09-30T04:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/bb/33/2a6ae7fdc1b10cb581cef91addd8cdfc5f40d50abb5702309369d5834579/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93", size = 268757, upload-time = "2026-09-30T04:37:19.877Z" }, + { url = "https://files.pythonhosted.org/packages/a2/22/80992720a0282cd39bba1db35868e6b9c22f41281160143a836544bc1d8a/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915", size = 262220, upload-time = "2026-09-30T04:37:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/92/9f/181fd07e1bffea1d95cd80c84ac537354f50699c22cfc4d3c02b6fc16208/charset_normalizer-3.5.2-cp314-cp314t-win32.whl", hash = "sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5", size = 197797, upload-time = "2026-09-30T04:37:23.235Z" }, + { url = "https://files.pythonhosted.org/packages/49/1c/25d8415ec1c4f2f41f1680435e4c87cfb378ff2f677d950946f2a45d0632/charset_normalizer-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc", size = 222765, upload-time = "2026-09-30T04:37:24.891Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b4/46b48f013dadfc0d0d33b375438e31bdf5a989dc68389c6bf627054d4df9/charset_normalizer-3.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105", size = 210985, upload-time = "2026-09-30T04:37:26.634Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e9/34e597dee616d0b8ee4b34d29399e85c2204ade174157a48505d42baa4ff/charset_normalizer-3.5.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26", size = 367676, upload-time = "2026-09-30T04:37:28.329Z" }, + { url = "https://files.pythonhosted.org/packages/60/9f/a5d1c91c0263745e2cd344c5a4415d787c575501ab1d449f1148ac6b495d/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364", size = 248414, upload-time = "2026-09-30T04:37:30.167Z" }, + { url = "https://files.pythonhosted.org/packages/26/79/e697f77464748a3ee3cf490c83d592459400d4898380d66c38366b03080c/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253", size = 244775, upload-time = "2026-09-30T04:37:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/3d42a42e18ea066e2513936fd678a00696e77878b5ae04528976abdbcb83/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0", size = 273877, upload-time = "2026-09-30T04:37:33.661Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/8a28136f3938ba9836f84280ce0c4d61ed1cf15a036b2034900c62634162/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc", size = 269455, upload-time = "2026-09-30T04:37:35.573Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a1/4fbf5d0f0f1b2a080474c1cf9a2f12c4c6531bb0e8ba591055e846d2b4e9/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229", size = 257336, upload-time = "2026-09-30T04:37:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a2/8b50aa320adb880ad579518e6f718f24944804b42a88b83d267d5d444125/charset_normalizer-3.5.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5", size = 257584, upload-time = "2026-09-30T04:37:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/a5/57/50e3fed84e175f40349bd0da7a4fce94c87f0378f52d74f511d89e0bdc20/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98", size = 251854, upload-time = "2026-09-30T04:37:41.23Z" }, + { url = "https://files.pythonhosted.org/packages/d6/54/f7fbb3493c9f49091213b9c2d6dd65800696f1ce1a3f196a4205f50417b1/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3", size = 248767, upload-time = "2026-09-30T04:37:42.883Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/b3a6385acc5a1e45b39ae9c90bfb9cf838a09b9dd37ef2740ab4c6b4a2eb/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2", size = 274479, upload-time = "2026-09-30T04:37:44.658Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/809913e2cfd279e635a9294fdbbfb1b1dc62a8189d473d561f649fce98d8/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf", size = 258310, upload-time = "2026-09-30T04:37:46.529Z" }, + { url = "https://files.pythonhosted.org/packages/af/a2/f28400ab13359d91bd39179df8e149376b9bf36588e739a3a4f9de2b84b2/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95", size = 271100, upload-time = "2026-09-30T04:37:48.399Z" }, + { url = "https://files.pythonhosted.org/packages/e9/89/9bab37955edf0adb3b66f8a3a6617d9f2f487e0d56f295a6a286cb640aa6/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d", size = 259936, upload-time = "2026-09-30T04:37:50.023Z" }, + { url = "https://files.pythonhosted.org/packages/23/b5/4459e08d45a679f903d50fea08bc52cfa728cca4d7bd02c757b5e5abda2e/charset_normalizer-3.5.2-cp315-cp315-win32.whl", hash = "sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847", size = 186301, upload-time = "2026-09-30T04:37:51.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/e8/55d5fd3935b4bce6da4fe0df61898e8c82653e317e677bd58aceb9c60f13/charset_normalizer-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8", size = 210858, upload-time = "2026-09-30T04:37:53.427Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/974423c2fd8e524c7a7f64318c1e02240ef954912fa2b4d70344107b9c68/charset_normalizer-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a", size = 200309, upload-time = "2026-09-30T04:37:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f9/00ee0195db1013d8f7c416fd770fbeb560bb46eb2e36b054d05cb56f6cfa/charset_normalizer-3.5.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1", size = 395343, upload-time = "2026-09-30T04:37:56.743Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/c00b50e94c964cf934c7899cd47c97952fc11dad71cc5884b3c61795b09b/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b", size = 250114, upload-time = "2026-09-30T04:37:58.607Z" }, + { url = "https://files.pythonhosted.org/packages/50/27/d102dc880bbcffd0479ab64dfc1fb96777a854355a55e2bda72a71efadcb/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f", size = 239997, upload-time = "2026-09-30T04:38:00.511Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4a/bf7ef45794dd293fab5f98a9309817977fbb845b9998f171b8cc5d8437a3/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3", size = 272313, upload-time = "2026-09-30T04:38:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ee/008a2837737991474c5754bb3191010007663860979701990982a502cbaf/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e", size = 269986, upload-time = "2026-09-30T04:38:04.435Z" }, + { url = "https://files.pythonhosted.org/packages/93/ad/bd74a283940dc910c5b14f8e4f80a248082bc9c0fcbe1f54530cb6d9cc5e/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9", size = 260202, upload-time = "2026-09-30T04:38:06.549Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7b/ed341c66f69f688723501fac752be3d63c7159ca0d0d4174fc611e5710bb/charset_normalizer-3.5.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a", size = 259083, upload-time = "2026-09-30T04:38:08.311Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9d/e41588b777965e5031a43128a1e96173ebb35ac75fc53ec3b517e7c21cd4/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115", size = 253081, upload-time = "2026-09-30T04:38:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/81/35/b761eb6d8c1eb218b9b42b9b4d5ac902afdc399fb6dac6f9a9aac7bda589/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c", size = 248712, upload-time = "2026-09-30T04:38:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2c/147169a041b747759f37405c0a97157e8e92de967968373101ff14915cba/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d", size = 274176, upload-time = "2026-09-30T04:38:14.138Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2d/0ff8db0d373ba8538db686db11cd7e8912031490b9e4f383b41912e8d594/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d", size = 259413, upload-time = "2026-09-30T04:38:15.841Z" }, + { url = "https://files.pythonhosted.org/packages/8a/8e/b4a085fb47c9d3a7e43576a4784fdd8fe23f907514a972de8086edaf7a48/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4", size = 269434, upload-time = "2026-09-30T04:38:17.626Z" }, + { url = "https://files.pythonhosted.org/packages/83/1c/d8d8d7322a7c3eecdf3237a4a419cf41d2eaad8e006ce7dfdd9d4c8fa2eb/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b", size = 262716, upload-time = "2026-09-30T04:38:19.214Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/0e4c6ba9b44e97a2da150e52d331e8f9c968b21b358fbffa6c856cebcd89/charset_normalizer-3.5.2-cp315-cp315t-win32.whl", hash = "sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800", size = 197159, upload-time = "2026-09-30T04:38:21.037Z" }, + { url = "https://files.pythonhosted.org/packages/be/33/e90bc2b1374f7f36ef106f56620de5a783907e19ca857efe2277e31cac3e/charset_normalizer-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21", size = 221322, upload-time = "2026-09-30T04:38:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/66/89/dfa6dcb08c200b7830ab56439e8c1890f2971d51aafbb3937894a2e7fcfc/charset_normalizer-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58", size = 209192, upload-time = "2026-09-30T04:38:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ab/176fbfd5b64939c55d652366aa5b9ef1d767af207a3aa6ebeb0d226c484d/charset_normalizer-3.5.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd", size = 331815, upload-time = "2026-09-30T04:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/7e/84/371eac6b30bdbcbf2d632a1a01809103459216fcaae61b8b8d922c1bfb8a/charset_normalizer-3.5.2-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7", size = 253276, upload-time = "2026-09-30T04:38:28.032Z" }, + { url = "https://files.pythonhosted.org/packages/43/6f/c4fbae58febff71709c51bc7e18fdfa55341dc382704740f9f0cbf03817b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f", size = 241239, upload-time = "2026-09-30T04:38:29.732Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/458c3f42164a07d0c5210798e9e704b39e540a6793b05aba67f3a35243a9/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93", size = 231121, upload-time = "2026-09-30T04:38:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/ab9e89367076f6331bb6c65c4bf14a5361fa5191cb6561bf534f18504e1b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade", size = 260350, upload-time = "2026-09-30T04:38:33.239Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/061431ecc688d9d76602502cb57cc01e691e682c18f1beb45f9673b5bbd2/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0", size = 255430, upload-time = "2026-09-30T04:38:34.865Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1f/20c8949f0676f7ab811abdeb7f4d7f1cbc6e61ff20bef08b44edeb092bc8/charset_normalizer-3.5.2-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26", size = 250612, upload-time = "2026-09-30T04:38:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/46f2fa4c431fc98c4ae76a8cb5bdca54e0341e3cfc3fcfd8e82740250818/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011", size = 242083, upload-time = "2026-09-30T04:38:38.26Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/559be29a0c0f086e0bba6922babd38916cc5e0b58ced4de13ee01ea05508/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621", size = 232738, upload-time = "2026-09-30T04:38:39.81Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6c/387b0e4f756a282831c1d9fc6aeb6c51ca4507ca202767c8de15ce9b12e2/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4", size = 260703, upload-time = "2026-09-30T04:38:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/1fdf015f09ef449f50d3ac4b67c90887c9c318b727daa95cc4f866e6521d/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e", size = 247622, upload-time = "2026-09-30T04:38:42.937Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3c/8e7b8a5671ad5d433669fb2a76f1a0164df2d9b1718b0206bc2a16d840cc/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c", size = 257500, upload-time = "2026-09-30T04:38:44.604Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f0/45b579df5cabc1d5d53ea1cc35e8437d3ca768c0acccc7041517cb6fbb32/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0", size = 255100, upload-time = "2026-09-30T04:38:46.289Z" }, + { url = "https://files.pythonhosted.org/packages/31/68/fdec18a343f5fb3f310588dd478b09ac4799e0b187dbade3a8cd776f03ef/charset_normalizer-3.5.2-cp37-abi3-win32.whl", hash = "sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf", size = 174499, upload-time = "2026-09-30T04:38:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8a/b618149cc5207943a0242068d7a27897f56a62947b5a039085f2a22029f8/charset_normalizer-3.5.2-cp37-abi3-win_amd64.whl", hash = "sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036", size = 200092, upload-time = "2026-09-30T04:38:49.707Z" }, + { url = "https://files.pythonhosted.org/packages/03/cf/4c66866fa9e2b1c78e3c911516d1de497a677b7ac60f1eceda74ce777ca3/charset_normalizer-3.5.2-cp37-abi3-win_arm64.whl", hash = "sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e", size = 294363, upload-time = "2026-09-30T04:38:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ad/d07d7862a62ffa6d79d68074d14823243dd235a77c45262acbf6adeb28bf/charset_normalizer-3.5.2-py3-none-any.whl", hash = "sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685", size = 68872, upload-time = "2026-09-30T04:39:21.828Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, ] [[package]] name = "colorama" version = "0.4.6" -source = { registry = "http://pypi.i.h.pjlab.org.cn/brain/dev/+simple" } +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 = "cryptography" +version = "50.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/56/d194340cc4a57535e82e1bee9e89667ac4b7c13b5d3f59686deae3094dd5/cryptography-50.0.2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fa8f5efb344d6908a1ce62f4a24e2e5780f825d6f53f5f50ec5ffacac72936cb", size = 3914904, upload-time = "2026-09-30T14:43:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2e/7212ca32fd43dc91f2f41db20160b268098874b4c9a0e7be94d6835f5b2e/cryptography-50.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a582ab2ae1d34f67112cadc86702774c9ea4374df6bca6afe672817203c99134", size = 5367029, upload-time = "2026-09-30T14:43:52.911Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" }, + { url = "https://files.pythonhosted.org/packages/8f/30/e86d7d518489b0ae2497091a35287abcb1a2ce4037837a34afbe9b1d6964/cryptography-50.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f265528741e048bce55c3463ed721fb0aa45a5888d8add8cfeccb3035451bbdc", size = 5324103, upload-time = "2026-09-30T14:44:01.901Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" }, + { url = "https://files.pythonhosted.org/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4f/adfc442765721292fff86d314ce385d3249d22db42295c0dd057727b60f3/cryptography-50.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:7afa5a6602a9f29af1f3a2965f831bae7c9d5d597b7cbb716d41ab3b7d89879c", size = 3824692, upload-time = "2026-09-30T14:44:11.671Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/52eb3770c0d0be2702a98c6e96065ddc0a2877cf0845aa9c23397c142cd4/cryptography-50.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f785f6161f202ab04d8ca194158968798e480ca058943907972da5f12e2881e8", size = 3892731, upload-time = "2026-09-30T14:44:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" }, + { url = "https://files.pythonhosted.org/packages/f4/37/cce70f150c432914460157a6ecc161752e053aa5ec0ef3b3f7dc6e31039a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:94e5e9f108ee10471288214d3d233fbfbb492840a8457eb85178d643ddeb32c7", size = 5328676, upload-time = "2026-09-30T14:44:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" }, + { url = "https://files.pythonhosted.org/packages/30/96/01546c7f69ea0e2ab790a2e4f0934a4052fb9b388147fbf83c2fd72f1e57/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:84f964e537f916e2cc85199e5a88742e964939b575ac8598b3f9d6cc416cdaf1", size = 5285085, upload-time = "2026-09-30T14:44:30.704Z" }, + { url = "https://files.pythonhosted.org/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" }, + { url = "https://files.pythonhosted.org/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cc/1d33befb3cd7ea7e77d2d73f43f2066471da1b21f24a6156efcaabf6d2e8/cryptography-50.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:78198641e5be9521beea5aa782bb551a58068d10e6eb04c9c680c1b69f2e7d45", size = 3795868, upload-time = "2026-09-30T14:44:39.71Z" }, + { url = "https://files.pythonhosted.org/packages/2d/49/93f6a6e7a87c9aa68d44d3e1cdb5fe8f60c90d5d2f46acae9a56892816b8/cryptography-50.0.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:edc3342adf8f697fc5f59c887a304356f147b397809440ed64e2fa6af2f50f37", size = 4133708, upload-time = "2026-09-30T14:44:41.807Z" }, + { url = "https://files.pythonhosted.org/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/9ded6d51ddd9897f6b6e81fb9ebea7951d7cc5d6c890b0ed8abf77a51a80/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_ppc64le.whl", hash = "sha256:b13478603dcd0a2479ff8e87e2c19a7d525734686fe3c49542472293a204212d", size = 5548822, upload-time = "2026-09-30T14:44:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" }, + { url = "https://files.pythonhosted.org/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/196f112617fb08eb4d608a2a6c422373d46f9cc2857f38fc0667033c0899/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_ppc64le.whl", hash = "sha256:cbc8738fd8526d80f35cb3a40d41f41a2e7030bb3b18b09a6778ef63d291c2fd", size = 5506351, upload-time = "2026-09-30T14:45:02.267Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" }, + { url = "https://files.pythonhosted.org/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/55f8a3f2ef5d1529e16835ef10cf0fe3d559ce237b46dddc440c0bba3649/cryptography-50.0.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:c423ab384a46c4dff7217b2ea5ba2e11cffdeab6441acd04cf65a369caf0366c", size = 4111557, upload-time = "2026-09-30T15:29:20.155Z" }, + { url = "https://files.pythonhosted.org/packages/23/ad/ac987755d00e1e64273760228d2635ae38dae2be83e3c6e0d3289d91dec3/cryptography-50.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0ec5f09541743261e66e291b4a0cbf0fb2997aeaab6d9e9c740b9dba1b58d1c2", size = 3943588, upload-time = "2026-09-30T15:29:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" }, + { url = "https://files.pythonhosted.org/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" }, + { url = "https://files.pythonhosted.org/packages/fc/35/b345bdfa40c9126df1a9d33236aa98418367931b8725f84fc3ae2b98dc59/cryptography-50.0.2-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:58ddb5a8e3179d12f19e4ea34d2d32e9d63a4baa142c875c1eb59f41b7243acd", size = 5382217, upload-time = "2026-09-30T15:29:30.589Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" }, + { url = "https://files.pythonhosted.org/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3f/86ff33ce34cc0de6847fb96e035a1a760d81652e38643f617c02ad32ef7a/cryptography-50.0.2-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a6557e5f38e065ca9fbdaf7cfc7435ecb1d113aa81a022d1b51921ee7432e227", size = 5338560, upload-time = "2026-09-30T15:29:39.053Z" }, + { url = "https://files.pythonhosted.org/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7a/f08d34ce09d60f89ebd391e2ebc6ba2b995e6dd7552f41820f8085f94e53/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:92e665960f25fcdc73725b9cec7a3824f279ba97a98653afe9ffac2e43668f67", size = 4716035, upload-time = "2026-09-30T15:29:48.681Z" }, + { url = "https://files.pythonhosted.org/packages/45/67/e18fb65592451a2acb76e9f2fbe14e0f47a8318b4c5430f1633851d03daa/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eef4c2f3423810b3070ab391f85436d2f8bbfcb286ac15cbc73190b3563b1f1a", size = 4726917, upload-time = "2026-09-30T15:29:50.608Z" }, + { url = "https://files.pythonhosted.org/packages/83/28/38fdce17e60f6b825e69fc3b7f75e70a6612759980704697e1de4cbfaf6e/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:7c6d0330c472d96f6a6afe24d80dfdf15176c33096f0a4397ae4c60f3dd3be48", size = 4715341, upload-time = "2026-09-30T15:29:52.522Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/d9121a717e0f893c64bd6ca7702614778d7df2a5c309128a002421788516/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:1ba34f04897fcdaa73f74145c25f3ec146fbd56593853e88adc2e811303c5f42", size = 4726322, upload-time = "2026-09-30T15:29:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/e6d153808bf353e152abd2fd4d8f09670d956ac78379ac46e60d7efbf04c/cryptography-50.0.2-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:3dc4fd8058cea1644971207d530e1a03a184a805ffc8ebdddf0599d78a331b81", size = 3873097, upload-time = "2026-09-30T15:29:56.097Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1d/1271f287ff7170ddafc2aad36260c4eec20ccd2fea70f38455e9d56d427b/cryptography-50.0.2-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:7b75de3c8b3be1cdb1052747c929440c3eea46c1bc2cb8a6e3a48388e9b7b452", size = 3805376, upload-time = "2026-09-30T15:29:58.729Z" }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } wheels = [ - { url = "http://pypi.i.h.pjlab.org.cn/brain/dev/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, +] + +[[package]] +name = "dirhash" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "scantree" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/70/49f93897f3a4f7ab5f20a854ebc91aad47854e9fb2cd169e3a4452fa3f5e/dirhash-0.5.0.tar.gz", hash = "sha256:e60760f0ab2e935d8cb088923ea2c6492398dca42cec785df778985fd4cd5386", size = 21377, upload-time = "2024-08-03T22:14:13.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/1f/c8bf92552b7f0a13b9f12b85e3de8df6d9814240e0f8ce8f37433df028b3/dirhash-0.5.0-py3-none-any.whl", hash = "sha256:523dfd6b058c64f45b31604376926c6e2bd2ea301d0df23095d4055674e38b09", size = 13119, upload-time = "2024-08-03T22:14:11.688Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] [[package]] name = "exceptiongroup" version = "1.3.1" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "fastapi" +version = "0.157.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/7b/f9540549feb36e84c3420aea470148d0f0bcd1e9d1aec5b36216f3f31738/fastapi-0.157.0.tar.gz", hash = "sha256:460d48b9fe6fabbb785630fc5b256710bc82723b597b4bfbd6e4470ae5bb20f4", size = 504894, upload-time = "2026-10-21T13:56:33.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/c3/759716158338892ed8e483077241cdf80e1fd727f56baf750c75bfea58de/fastapi-0.157.0-py3-none-any.whl", hash = "sha256:45b5cbf098ce3f47e008ca67931ce333d028129c823e3b6766a3bd0d70b81f7f", size = 147897, upload-time = "2026-10-21T13:56:31.895Z" }, +] + +[[package]] +name = "fastuuid" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232, upload-time = "2025-10-19T22:19:22.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/b2/731a6696e37cd20eed353f69a09f37a984a43c9713764ee3f7ad5f57f7f9/fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6e6243d40f6c793c3e2ee14c13769e341b90be5ef0c23c82fa6515a96145181a", size = 516760, upload-time = "2025-10-19T22:25:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/c5/79/c73c47be2a3b8734d16e628982653517f80bbe0570e27185d91af6096507/fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:13ec4f2c3b04271f62be2e1ce7e95ad2dd1cf97e94503a3760db739afbd48f00", size = 264748, upload-time = "2025-10-19T22:41:52.873Z" }, + { url = "https://files.pythonhosted.org/packages/24/c5/84c1eea05977c8ba5173555b0133e3558dc628bcf868d6bf1689ff14aedc/fastuuid-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b2fdd48b5e4236df145a149d7125badb28e0a383372add3fbaac9a6b7a394470", size = 254537, upload-time = "2025-10-19T22:33:55.603Z" }, + { url = "https://files.pythonhosted.org/packages/0e/23/4e362367b7fa17dbed646922f216b9921efb486e7abe02147e4b917359f8/fastuuid-0.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f74631b8322d2780ebcf2d2d75d58045c3e9378625ec51865fe0b5620800c39d", size = 278994, upload-time = "2025-10-19T22:26:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/b2/72/3985be633b5a428e9eaec4287ed4b873b7c4c53a9639a8b416637223c4cd/fastuuid-0.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83cffc144dc93eb604b87b179837f2ce2af44871a7b323f2bfed40e8acb40ba8", size = 280003, upload-time = "2025-10-19T22:23:45.415Z" }, + { url = "https://files.pythonhosted.org/packages/b3/6d/6ef192a6df34e2266d5c9deb39cd3eea986df650cbcfeaf171aa52a059c3/fastuuid-0.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a771f135ab4523eb786e95493803942a5d1fc1610915f131b363f55af53b219", size = 303583, upload-time = "2025-10-19T22:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/9d/11/8a2ea753c68d4fece29d5d7c6f3f903948cc6e82d1823bc9f7f7c0355db3/fastuuid-0.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4edc56b877d960b4eda2c4232f953a61490c3134da94f3c28af129fb9c62a4f6", size = 460955, upload-time = "2025-10-19T22:36:25.196Z" }, + { url = "https://files.pythonhosted.org/packages/23/42/7a32c93b6ce12642d9a152ee4753a078f372c9ebb893bc489d838dd4afd5/fastuuid-0.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bcc96ee819c282e7c09b2eed2b9bd13084e3b749fdb2faf58c318d498df2efbe", size = 480763, upload-time = "2025-10-19T22:24:28.451Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e9/a5f6f686b46e3ed4ed3b93770111c233baac87dd6586a411b4988018ef1d/fastuuid-0.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7a3c0bca61eacc1843ea97b288d6789fbad7400d16db24e36a66c28c268cfe3d", size = 452613, upload-time = "2025-10-19T22:25:06.827Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c9/18abc73c9c5b7fc0e476c1733b678783b2e8a35b0be9babd423571d44e98/fastuuid-0.14.0-cp310-cp310-win32.whl", hash = "sha256:7f2f3efade4937fae4e77efae1af571902263de7b78a0aee1a1653795a093b2a", size = 155045, upload-time = "2025-10-19T22:28:32.732Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8a/d9e33f4eb4d4f6d9f2c5c7d7e96b5cdbb535c93f3b1ad6acce97ee9d4bf8/fastuuid-0.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ae64ba730d179f439b0736208b4c279b8bc9c089b102aec23f86512ea458c8a4", size = 156122, upload-time = "2025-10-19T22:23:15.59Z" }, + { url = "https://files.pythonhosted.org/packages/98/f3/12481bda4e5b6d3e698fbf525df4443cc7dce746f246b86b6fcb2fba1844/fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:73946cb950c8caf65127d4e9a325e2b6be0442a224fd51ba3b6ac44e1912ce34", size = 516386, upload-time = "2025-10-19T22:42:40.176Z" }, + { url = "https://files.pythonhosted.org/packages/59/19/2fc58a1446e4d72b655648eb0879b04e88ed6fa70d474efcf550f640f6ec/fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:12ac85024637586a5b69645e7ed986f7535106ed3013640a393a03e461740cb7", size = 264569, upload-time = "2025-10-19T22:25:50.977Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/3c74756e5b02c40cfcc8b1d8b5bac4edbd532b55917a6bcc9113550e99d1/fastuuid-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05a8dde1f395e0c9b4be515b7a521403d1e8349443e7641761af07c7ad1624b1", size = 254366, upload-time = "2025-10-19T22:29:49.166Z" }, + { url = "https://files.pythonhosted.org/packages/52/96/d761da3fccfa84f0f353ce6e3eb8b7f76b3aa21fd25e1b00a19f9c80a063/fastuuid-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09378a05020e3e4883dfdab438926f31fea15fd17604908f3d39cbeb22a0b4dc", size = 278978, upload-time = "2025-10-19T22:35:41.306Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/f84c90167cc7765cb82b3ff7808057608b21c14a38531845d933a4637307/fastuuid-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbb0c4b15d66b435d2538f3827f05e44e2baafcc003dd7d8472dc67807ab8fd8", size = 279692, upload-time = "2025-10-19T22:25:36.997Z" }, + { url = "https://files.pythonhosted.org/packages/af/7b/4bacd03897b88c12348e7bd77943bac32ccf80ff98100598fcff74f75f2e/fastuuid-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd5a7f648d4365b41dbf0e38fe8da4884e57bed4e77c83598e076ac0c93995e7", size = 303384, upload-time = "2025-10-19T22:29:46.578Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a2/584f2c29641df8bd810d00c1f21d408c12e9ad0c0dafdb8b7b29e5ddf787/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c0a94245afae4d7af8c43b3159d5e3934c53f47140be0be624b96acd672ceb73", size = 460921, upload-time = "2025-10-19T22:36:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/c6b77443bb7764c760e211002c8638c0c7cce11cb584927e723215ba1398/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b29e23c97e77c3a9514d70ce343571e469098ac7f5a269320a0f0b3e193ab36", size = 480575, upload-time = "2025-10-19T22:28:18.975Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/93f553111b33f9bb83145be12868c3c475bf8ea87c107063d01377cc0e8e/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1e690d48f923c253f28151b3a6b4e335f2b06bf669c68a02665bc150b7839e94", size = 452317, upload-time = "2025-10-19T22:25:32.75Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8c/a04d486ca55b5abb7eaa65b39df8d891b7b1635b22db2163734dc273579a/fastuuid-0.14.0-cp311-cp311-win32.whl", hash = "sha256:a6f46790d59ab38c6aa0e35c681c0484b50dc0acf9e2679c005d61e019313c24", size = 154804, upload-time = "2025-10-19T22:24:15.615Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b2/2d40bf00820de94b9280366a122cbaa60090c8cf59e89ac3938cf5d75895/fastuuid-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:e150eab56c95dc9e3fefc234a0eedb342fac433dacc273cd4d150a5b0871e1fa", size = 156099, upload-time = "2025-10-19T22:24:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164, upload-time = "2025-10-19T22:31:45.635Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837, upload-time = "2025-10-19T22:38:38.53Z" }, + { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370, upload-time = "2025-10-19T22:40:26.07Z" }, + { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766, upload-time = "2025-10-19T22:37:23.779Z" }, + { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105, upload-time = "2025-10-19T22:26:56.821Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564, upload-time = "2025-10-19T22:30:31.604Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659, upload-time = "2025-10-19T22:31:32.341Z" }, + { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430, upload-time = "2025-10-19T22:26:22.962Z" }, + { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894, upload-time = "2025-10-19T22:27:01.647Z" }, + { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374, upload-time = "2025-10-19T22:29:19.879Z" }, + { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550, upload-time = "2025-10-19T22:27:49.658Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720, upload-time = "2025-10-19T22:42:34.633Z" }, + { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024, upload-time = "2025-10-19T22:30:25.482Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679, upload-time = "2025-10-19T22:36:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862, upload-time = "2025-10-19T22:36:23.302Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278, upload-time = "2025-10-19T22:29:43.809Z" }, + { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788, upload-time = "2025-10-19T22:36:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819, upload-time = "2025-10-19T22:35:31.623Z" }, + { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546, upload-time = "2025-10-19T22:26:03.023Z" }, + { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921, upload-time = "2025-10-19T22:31:02.151Z" }, + { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559, upload-time = "2025-10-19T22:36:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539, upload-time = "2025-10-19T22:33:35.898Z" }, + { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600, upload-time = "2025-10-19T22:43:44.17Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069, upload-time = "2025-10-19T22:43:38.38Z" }, + { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543, upload-time = "2025-10-19T22:32:22.537Z" }, + { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798, upload-time = "2025-10-19T22:33:53.821Z" }, + { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283, upload-time = "2025-10-19T22:29:02.812Z" }, + { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627, upload-time = "2025-10-19T22:35:54.985Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778, upload-time = "2025-10-19T22:28:00.999Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605, upload-time = "2025-10-19T22:36:21.764Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837, upload-time = "2025-10-19T22:34:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532, upload-time = "2025-10-19T22:35:18.217Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457, upload-time = "2025-10-19T22:33:44.579Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/59/e19834834cb01a32febfbb0f8a23a9088088f5d45991824ff2bc3b5e8acb/filelock-3.32.7.tar.gz", hash = "sha256:37b8a3d9811b0f9aef7e5ec5c71bb320de52df51e6ca9bcd6f5ad81187660da7", size = 225154, upload-time = "2026-09-16T00:24:20.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/df/31098c5aeb4d966b553641472bd55fcf5fdfac953549894b8a765ba44e91/filelock-3.32.7-py3-none-any.whl", hash = "sha256:65ff0d0190ea42038b32bda4b77834fb05be2cad4c5b9b01aa4dfb3614536e52", size = 100157, upload-time = "2026-09-16T00:24:19.543Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/cd/9be253869fc42e764de7f3dedd6969af7d44ff9c3375214a3442a6f3fc08/fsspec-2026.9.0.tar.gz", hash = "sha256:0f08147951c8cb31d844c3547d631053b127863b60be04cf06e121333ee0e2fe", size = 333545, upload-time = "2026-09-18T17:50:42.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/c0/a98505f18594f1bce828bb159cec0fcf9860562f1a2c85913409fc8f3d9e/fsspec-2026.9.0-py3-none-any.whl", hash = "sha256:8dd6e646e99ea382bd85f97a45e6b526a442d79423a7dc673f1e2756d05fcb5f", size = 221738, upload-time = "2026-09-18T17:50:41.341Z" }, +] + +[[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 = "h2" +version = "4.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/85/7c366e69d84c17bb778fe41419e1fbcce3033d5b7ce29bbffff0a98b859f/h2-4.4.1.tar.gz", hash = "sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516", size = 2157281, upload-time = "2026-08-03T11:45:09.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/22/e85faf23bd72a92d1921e37d674ca56eb298a3c8be31fdecef0ff2b3aaac/h2-4.4.1-py3-none-any.whl", hash = "sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6", size = 62636, upload-time = "2026-08-03T11:44:59.164Z" }, +] + +[[package]] +name = "harbor" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dirhash" }, + { name = "fastapi" }, + { name = "filelock" }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "litellm" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pydantic" }, + { name = "pyjwt" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, + { name = "shortuuid" }, + { name = "supabase" }, + { name = "tenacity" }, + { name = "toml" }, + { name = "typer" }, + { name = "uvicorn" }, ] -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/exceptiongroup/1.3.1/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/3a/b05d0e8752d5d531e56fbb2955f0e656c0a9b5fbcd202dbad7313b29d4df/harbor-0.23.0.tar.gz", hash = "sha256:a8b87e98db59877228bb36d2c24757cc422a3144ffaf31a1f88955c65c911e4d", size = 1907464, upload-time = "2026-09-12T04:55:11.919Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/exceptiongroup/1.3.1/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598" }, + { url = "https://files.pythonhosted.org/packages/19/c7/607ff037dff1f40d1f941b9854742d66fd43630274fd4e7b8de8480dad34/harbor-0.23.0-py3-none-any.whl", hash = "sha256:8747400dbb2a5e2298e1338e17e88eba38433c0433fd700f34d1a9021bba5c37", size = 2142100, upload-time = "2026-09-12T04:55:10.014Z" }, ] [[package]] @@ -42,61 +984,1163 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "httpx" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "ruff" }, ] +sandbox = [ + { name = "harbor", marker = "python_full_version >= '3.12'" }, + { name = "httpx" }, + { name = "opensandbox", marker = "python_full_version >= '3.12'" }, +] [package.metadata] requires-dist = [ + { name = "harbor", marker = "python_full_version >= '3.12' and extra == 'sandbox'", specifier = "==0.23.0" }, + { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.27,<1" }, + { name = "httpx", marker = "extra == 'sandbox'", specifier = ">=0.27,<1" }, + { name = "opensandbox", marker = "python_full_version >= '3.12' and extra == 'sandbox'", specifier = "==0.1.9" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24" }, { name = "pyyaml", specifier = ">=6.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8" }, { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }, ] -provides-extras = ["dev"] +provides-extras = ["sandbox", "dev"] + +[[package]] +name = "hf-xet" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/27/06d899ea7bd721d272f84aac98bdb238de98af4cc767a69056d967d68c71/hf_xet-1.7.0.tar.gz", hash = "sha256:d406ec79053c0871817f700c2ac8c36ba0d87f9c34b7458b0f0063bb218b0466", size = 985689, upload-time = "2026-10-06T20:18:43.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/7c/3e45174942e6793adde6cba4daa7fb037275cf02a944d9eadfcf9ff33b86/hf_xet-1.7.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:fa029678be1ba7f953c409b0b27bf15cc69cd1c9b3a674fbd78856ebefca1052", size = 3803919, upload-time = "2026-10-06T20:18:09.844Z" }, + { url = "https://files.pythonhosted.org/packages/ff/3a/5e8b363391adcbb002e191dbf924dab31464ea9c45adfeb73502afc36d35/hf_xet-1.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57bc157b8b7fe3bee9dcb9af7f3da8de41801c3b31a9ef68a77a33c6a6be382f", size = 3553588, upload-time = "2026-10-06T20:18:13.376Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c2/0d1eaa5da13bbf9c896badc7f380601c7d973a87a6ffb4d100267c4536c1/hf_xet-1.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:87dab080f8f7d32781c2586904e3603f4e60d09bfc727706c3ae419e0829beeb", size = 4201962, upload-time = "2026-10-06T20:18:16.11Z" }, + { url = "https://files.pythonhosted.org/packages/23/2d/225d5b11a9ca7d31b9470a57f2b2be1a5cef8b84325a2146aeb4589e226c/hf_xet-1.7.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b01fe18dbbd151a2403d2c64ed30dc6547b00d6babab9a617d77c7acdb81ee66", size = 3982978, upload-time = "2026-10-06T20:18:18.092Z" }, + { url = "https://files.pythonhosted.org/packages/93/34/9d681f0e3dac0b5dae0d7dea748429266f24e52415446523f464fbaa828e/hf_xet-1.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4ee5e05a627f5ab5bad7a86582277d645556ea1e199903aae19e033a392aa13a", size = 4181558, upload-time = "2026-10-06T20:18:20.082Z" }, + { url = "https://files.pythonhosted.org/packages/de/f0/277f039b7d72027bc2ed277f1b62a2f70f740a5aac2a3e7243e5b6854c5d/hf_xet-1.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:19c0e64f14175ccb6a1aff69e0d2ab9ec5269a560e6687abaf2b3fa4f73de7cd", size = 4411546, upload-time = "2026-10-06T20:18:21.999Z" }, + { url = "https://files.pythonhosted.org/packages/3d/7f/832d3ddb49326114175b7bcc50daea8565c09fd21ac03a02b211c09fefb7/hf_xet-1.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:757168feb5679647c0bb13ee5d0faebe799c4dff9051419885a566ebd79f949d", size = 3812809, upload-time = "2026-10-06T20:18:24.288Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c4/310c3c29e5beae7c049e63947bd1923d597883b41c9ec4718589920812c4/hf_xet-1.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b91569d5f1b61c34b043687da02c05dd3604f3d329e7868510bf3f7971599006", size = 3646174, upload-time = "2026-10-06T20:18:26.279Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0b/b03be21ffaada749ba0d3197d8aefbf1aa698bac149580421c15239b299e/hf_xet-1.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e3e88a7a75d7d95cbee1f37dc31341d6201124cf21c6c4b1dfab8ccba9b09e0f", size = 3796096, upload-time = "2026-10-06T20:18:28.43Z" }, + { url = "https://files.pythonhosted.org/packages/c3/47/a26ebdce7056a61e931f228439bc0ab08cbec239d1690f965e5e637cba79/hf_xet-1.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:59fba37039233c7fcbe196817d6cdcf1b40dfb17b410f229d85b0cf0a1848da4", size = 3560352, upload-time = "2026-10-06T20:18:30.365Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4c/2bf3b66c215d409655f28de1622393dde04c9461280d48c7924bb3b2decd/hf_xet-1.7.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2814a6e999d13464c4d679b788cc5d784eb5a4edfc638a31f10e9a11ab531ef8", size = 4212180, upload-time = "2026-10-06T20:18:32.292Z" }, + { url = "https://files.pythonhosted.org/packages/49/0c/a2f703a5a78267556e89e03316fa0805c86b72b50829bc67665746e8ebf0/hf_xet-1.7.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fcfd6c22418e57dd5b3aea649e813b2e2cfb2aebf317b210d90f1fe4b3018b52", size = 3990011, upload-time = "2026-10-06T20:18:34.21Z" }, + { url = "https://files.pythonhosted.org/packages/a4/77/e52e4201b1cbf571530a61cc57f70182045a39a230089ee5f1df182a4de2/hf_xet-1.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80f79dae613ce9e0ea1fd1ae15616ca9ac74aed4c770aabc199c4f03ebecc863", size = 4190628, upload-time = "2026-10-06T20:18:36.062Z" }, + { url = "https://files.pythonhosted.org/packages/6c/dc/03a21b89f118664a0926ff25b0f8e44a519bf22724a6a8fc7a9abbc188b6/hf_xet-1.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0a9e802f33bf50c851abe45fc5380e61f959e2d369647d6742b79ad9d6c27cab", size = 4418814, upload-time = "2026-10-06T20:18:37.888Z" }, + { url = "https://files.pythonhosted.org/packages/4d/59/b35106dfa71b6eef605dc88bd038fe99c7f86fb132a15b60d0bf2f235b2c/hf_xet-1.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:2b7bb5727889b0f2436dbaaad8fc4c3e66b8240d992716989e0c086b4278b1bc", size = 3822644, upload-time = "2026-10-06T20:18:40.052Z" }, + { url = "https://files.pythonhosted.org/packages/48/cd/072313585f74fe9d441e2eb5e0a4703c30586cd709810ea369675f61b74e/hf_xet-1.7.0-cp38-abi3-win_arm64.whl", hash = "sha256:acc3851cf2576a8fb2ae926da863f4efabe21303cf292e9a44332802ab0dcc6a", size = 3662436, upload-time = "2026-10-06T20:18:42.205Z" }, +] + +[[package]] +name = "hpack" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/5b/fcabf6028144a8723726318b07a32c2f3314acdff6265743cf08a344b18e/hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0", size = 51300, upload-time = "2026-06-23T18:34:46.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/b4/4a9fcfb2aef6ba44d9073ecd301443aa00b3dac95de5619f2a7de7ec8a91/hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986", size = 34246, upload-time = "2026-06-23T18:34:45.472Z" }, +] + +[[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.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.33.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/25/2a/484d112c0d8fc5f665d7b65137ac9cdb2953c982391598c3597968a12ee7/huggingface_hub-1.33.0.tar.gz", hash = "sha256:367be21a201db9523eddf8aeac7048f2602c1b308691c97640d5e72ed188007e", size = 1043548, upload-time = "2026-09-24T09:49:29.971Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/16/963096d224b80909432dc16561a615fd33d2d13beef3ce4c63fa25e40867/huggingface_hub-1.33.0-py3-none-any.whl", hash = "sha256:04e434b06e100eddbce9a6e817d72693a7884b10a79bd67ab48080d5c07eb899", size = 846435, upload-time = "2026-09-24T09:49:28.059Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/72/c600ae4f68c28fc19f9c31b9403053e5dbb8cace2e6842c7b7c3e4d42fe9/importlib_metadata-8.9.0.tar.gz", hash = "sha256:58850626cef4bd2df100378b0f2aea9724a7b92f10770d547725b047078f99ee", size = 56140, upload-time = "2026-03-20T16:56:26.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/f9/97f2ca8bb3ec6e4b1d64f983ebe98b9a192faddff67fac3d6303a537e670/importlib_metadata-8.9.0-py3-none-any.whl", hash = "sha256:e0f761b6ea91ced3b0844c14c9d955224d538105921f8e6754c00f6ca79fba7f", size = 27220, upload-time = "2026-03-20T16:56:25.07Z" }, +] [[package]] name = "iniconfig" version = "2.3.0" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/iniconfig/2.3.0/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[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 = "jiter" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/1f/8176d92e001f86505424b41664032ae26a882bc9ca41a32c803f373f9195/jiter-0.17.0.tar.gz", hash = "sha256:03e432f226a453851079fb84cd17c6da9991eab723e28d716f14ae3d906e0c12", size = 229037, upload-time = "2026-09-12T15:14:14.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/fb/0b68a8666a203ab349334e502285153ac97e84a1a3e2f80de8385ed0899c/jiter-0.17.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed1a24005daac667d577402d75a2922f9775a165b146b883ff1ad3602d8be689", size = 290968, upload-time = "2026-09-12T15:11:10.44Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e9/5993079500530586655c6f2823e2926b3f5ceddd49918c5a86757eadba3a/jiter-0.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b847b18d066c46b3b7ae49d6c94a7634c5e4a8983146ee25562a092000f5e3ad", size = 324072, upload-time = "2026-09-12T15:11:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/6f5dbffaa5b0e647386e3c3376aa150987538f1b13f6da1a9582787957af/jiter-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b68d3495d95da120651a5628c7ebadee84ed001a1b76e6afc325c42482f15b5", size = 344891, upload-time = "2026-09-12T15:11:13.387Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9d/8dd5719add6c77be95b23c129682a1484c5339ffd66736d66bfa6831a2f3/jiter-0.17.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c1a5336c04a41b1f1cf9572e294aec27cc569767ff73de7bf87a91f0bea7cb9", size = 328213, upload-time = "2026-09-12T15:11:14.655Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e5/82f885863afaa79efac066efdb7b70bd242ef83d3e548453d61b3737e54a/jiter-0.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b75f85660108965a94be77911a25a253429307294d9415b3c597118977a614de", size = 343033, upload-time = "2026-09-12T15:11:16.046Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/ee3cd7db1704ec9c274ae549803ef1369c801490f7f6d48d0aa3b9ac1ae0/jiter-0.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32aaaa764604496610a3ad2d98503ae88ccb2fbe769e892ff4533e778e85f708", size = 351701, upload-time = "2026-09-12T15:11:19.188Z" }, + { url = "https://files.pythonhosted.org/packages/93/2c/af9ca503b38683a82b6732352a861515f28e2d46fe445aea57e07fc86e06/jiter-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:826871c42cebaae22f0a2b5673a4a1a75c851bb2d13b3c17764a630a6b298984", size = 348754, upload-time = "2026-09-12T15:11:20.486Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ba/692b071b194270b019b7d1790adb8658193379b951029e5ad15a14a2809d/jiter-0.17.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:00b5a98df3e3a3e8cf7b619f4ac2f8bf975bbf3d95d02c5d17b8dbfe5c8b8245", size = 329263, upload-time = "2026-09-12T15:11:21.701Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8f/35a22beb6b9eb025b7e2f88a38ca3b9c87017aac237feec2583eabe243ad/jiter-0.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6af5b74073bd25bae695e6d00919f6a9be7ed5a9f8836d981eb1ffe84139e6fb", size = 337572, upload-time = "2026-09-12T15:11:23.145Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0f/4341ceb3ce08199b4e8a798acc0bb0c79b3e861b6cecdb9d3b5138965c5d/jiter-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:16dd0c1baf098ae70b8f3616574eb3fedf34e26670b89e16a7e67561f737ed2d", size = 485174, upload-time = "2026-09-12T15:11:24.394Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6d/92fa138ac82db9001b68563df204c32f2203bee84301df083260039ca23b/jiter-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:545c36a0f3b2238c242cc9785439d3242a871b7bc39fe3f441bcaa07bf3aa83e", size = 522115, upload-time = "2026-09-12T15:11:25.645Z" }, + { url = "https://files.pythonhosted.org/packages/52/92/9b11557ddf898e84c253d1c42cc1bddd8ad1a754b46b01ae35a6a253526f/jiter-0.17.0-cp310-cp310-win32.whl", hash = "sha256:155be7355bdb7ca76ab0961be8982c225f964a5c073a83984183f22391cc29fc", size = 188163, upload-time = "2026-09-12T15:11:26.793Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f3/b4381cc962fed7d505913d5920e9f51f1fd5cf340a563134e8cadf3604d2/jiter-0.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:37150a9e02e869475854fa20b7d0d5e26d18d0f8bc17293999973ff27e99ae7a", size = 231679, upload-time = "2026-09-12T15:11:28.911Z" }, + { url = "https://files.pythonhosted.org/packages/a1/50/17afdaffcc8af4bf4fddf2b6c26d066553aa2221983f2affcde435fc2532/jiter-0.17.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cfafd7be8b16ceadd298db542cead37cddc211c4c49e04ad2596924df18625b1", size = 290699, upload-time = "2026-09-12T15:11:30.085Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/c185d32d5b3657ad84da26c84a9eb15f00aa1b39d6882fcc0052dba2d7c2/jiter-0.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8adca2e793288e5f1bb29279bb439d0d3cfbb50eddca7e7e6ffd42ff4f482406", size = 324246, upload-time = "2026-09-12T15:11:31.3Z" }, + { url = "https://files.pythonhosted.org/packages/24/7a/8b8903bfe91a90a8fa1ec9b45d9fda5b6287a386693b69d720a882d73f3c/jiter-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c692d567ba206c7cca38c9d1d0ccc70c9786290173c184d871ca12e9981ed7", size = 343275, upload-time = "2026-09-12T15:11:32.758Z" }, + { url = "https://files.pythonhosted.org/packages/a2/5d/6821fae2abc71a3c3a84bef8598d31fc4f27d9edfb55bd8f6c08afb8ef93/jiter-0.17.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81c83c0abe614446a283d994d2c07c4f58632dea2cdf66ba9e2921bb8ccd593e", size = 328034, upload-time = "2026-09-12T15:11:33.9Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/8e7a963b1c2dfdc01d6228b004f50a2a3d7c46f0549d7b096a5d15ef81d5/jiter-0.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:073dc68c1a700c8fc480e877864a6b6ffc887533e261f4380c08c16bf09d057a", size = 342457, upload-time = "2026-09-12T15:11:36.414Z" }, + { url = "https://files.pythonhosted.org/packages/73/27/8b2a267e3bda45d9298331cacfe3521e761f0a2b05ad10a23c6548d08358/jiter-0.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:492f37230bbf9581ab2c17bcda862c249afb9ae2e3ab2dd6db59943bc4cc3153", size = 351271, upload-time = "2026-09-12T15:11:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8f/5c74e5e142a6736833d7a991ab04d5c0738038dc44db05af0bb3cd2559e8/jiter-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5888fe5abc1ca2fa834a3e1b4c7ef0dcece286a7d7e95a609ef0934b777b9fc9", size = 346052, upload-time = "2026-09-12T15:11:39.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/9c/f54920f06d1696e80b1be841d56412871c6856b1b1e3b541b1ed35346554/jiter-0.17.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:84ac78df457e1ee3f7e733bd114823302ae8c5ad5542d7e6647d92ffaa090a04", size = 328915, upload-time = "2026-09-12T15:11:41.065Z" }, + { url = "https://files.pythonhosted.org/packages/21/53/080f126863bceb055db9f1fd5431485eb493b35545a3c35e961ac18cd924/jiter-0.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7573e80232c5bcf80c24c038cf7e53a463f5c3b1dd1dd4109d66304f4dccc233", size = 337240, upload-time = "2026-09-12T15:11:42.356Z" }, + { url = "https://files.pythonhosted.org/packages/f0/76/3ab742823a0e0e70e143c6c90a482d9d90396ac2445e7b9483eb7245d3b5/jiter-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:11902505d401691720f5785c15b02204248526edee11b635cd6c40cd52b81599", size = 484917, upload-time = "2026-09-12T15:11:43.549Z" }, + { url = "https://files.pythonhosted.org/packages/14/e0/8ca71bc8b9cc9ed96c9da565863e00f3bb875a8fb82abcf03e975e067902/jiter-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:64846211a2debe7c071d2146d2283d2b0c1c93dc8fd5fb7794faac2ca6061b5c", size = 521868, upload-time = "2026-09-12T15:11:44.704Z" }, + { url = "https://files.pythonhosted.org/packages/c1/02/81f8719dcedb75713082a2048405376c81f6546b75af8167f3bba01a1ed0/jiter-0.17.0-cp311-cp311-win32.whl", hash = "sha256:c19b9357309b8cc6de8a48fca8e44a8c9c2feaaa2f5896d037fa505d48fcab80", size = 187844, upload-time = "2026-09-12T15:11:45.874Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8c/59693f348488f01ed12d862e99ab8da14961152d3e9c39b9b1ef363f3572/jiter-0.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:e654b6b04e39c9cb19cb8b04c6ddf1f2db07751fa14156413969fd78bad0e5cb", size = 231402, upload-time = "2026-09-12T15:11:47.083Z" }, + { url = "https://files.pythonhosted.org/packages/fe/89/fb35e286463cb9f01edc2c4e47df6e5477ee36bca5096414e9ea87985588/jiter-0.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:3ad556afc289f15d2b181b941982d01f06190863c07440185b9f354e1bd2def3", size = 187006, upload-time = "2026-09-12T15:11:48.245Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f8/07bd8c3a23f7a8a6875e6a820bbffe1483a18f18f9398a91b5495123176e/jiter-0.17.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ebf918dfd6a74adc1b9ad71f63c4ab00902fcd3b7fd39f2e24d871db8d713b91", size = 291633, upload-time = "2026-09-12T15:11:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5e/0de4c6f84ffefa6809ffc2d550b9a314365acf7e7ec9b6c7375d49047900/jiter-0.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61aed66ee042b3b49ef85fdf75714234d055d89d8496ac1c6e47f89e7a30d5e4", size = 321695, upload-time = "2026-09-12T15:11:52.727Z" }, + { url = "https://files.pythonhosted.org/packages/20/ac/befe2e82065bee37a0252081666ed2f48c1ac5f5c6c318c2de8168ba393d/jiter-0.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76eb4a5c20e86f9f848286f167024890f2862258a965d254774deb7fc1545ca1", size = 341967, upload-time = "2026-09-12T15:11:54.231Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cd/9797c1e529746750ae589da7c1a8c24373f00d88e11a989f9e5eb1959079/jiter-0.17.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bcc064f99183a9cbe7f26ed648c352031a74145cd61ed75d34632c73eb46a5a8", size = 326546, upload-time = "2026-09-12T15:11:55.41Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fd/e6914c38d6347bab4ebff2b1f0c0f191db276e7a1d5c376176757da42fe3/jiter-0.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73b64e69c4150748e020356d958af94bec33c70a0a93d665cfa8f6d580fe1a63", size = 340995, upload-time = "2026-09-12T15:11:58.211Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7d/611b3abf6f88945b5474da5cdc6d1a185e805ac9bf446bb7766dcda6ea87/jiter-0.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0bc7f684b65bcda9c20434267577db71bf9905ceddd32b60d1d93278d8c8d3a", size = 352188, upload-time = "2026-09-12T15:11:59.414Z" }, + { url = "https://files.pythonhosted.org/packages/52/f8/b6e513ecbdf3b3cebe587c2279281ecf775b729a58cf4cc7bdf898ded029/jiter-0.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c21265b251d99bbb40080d178a8953e35601d3a1564e05c4de4c0d2ca616797", size = 345025, upload-time = "2026-09-12T15:12:00.697Z" }, + { url = "https://files.pythonhosted.org/packages/28/a8/fe26d06c5a6c5a4cfe703c5154c8a140da1305671eb3681aba9422d4f393/jiter-0.17.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:f3d7f7b34114f7ddc6d72a8e882d49de636b35d9fd12b4d420d3c5729f6c9812", size = 329180, upload-time = "2026-09-12T15:12:01.831Z" }, + { url = "https://files.pythonhosted.org/packages/e1/58/e6d66a26af40a20e62486feb7e222fd50f6e7aaa4f107abd89675dcc835b/jiter-0.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5078ab00664307fab2019b522a93aeb191122789f085daf5fd9e362154021d4a", size = 335805, upload-time = "2026-09-12T15:12:03.056Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3e/96520aa2fef5ef831d95483a902140bfab83dcac9eaa74f7df61b5e50a1b/jiter-0.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:470e1b1e4c42f1ead2189166a299691871a2df5056c976e7fb96feafaf5f9d44", size = 484121, upload-time = "2026-09-12T15:12:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8f/5d9d92fe538bf36ff481a2278c48147e59c1cf8eb2f7be665260665febe5/jiter-0.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6aedeb7352b8f3b6af9cbd67983840165c00428e63f1b420a85885128ea31", size = 521310, upload-time = "2026-09-12T15:12:05.612Z" }, + { url = "https://files.pythonhosted.org/packages/50/06/a09f979b22e652afbc3de66c709b2ba92edcef555f7535ab937c86b4f21a/jiter-0.17.0-cp312-cp312-win32.whl", hash = "sha256:362bb47423886d45a9f705d2d9d4008c6eedd4e41eb1bab4e96fb6daa06b33fd", size = 185029, upload-time = "2026-09-12T15:12:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d9/98265a005b2473ec2be5a84e2b64c2f65382c673879f1574845cd4bcd77c/jiter-0.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:9bd3caac219df476dd0cc3fe01d2f1581ed588906feac767abd9614c1c12f8b3", size = 227381, upload-time = "2026-09-12T15:12:08.823Z" }, + { url = "https://files.pythonhosted.org/packages/a8/11/2e05bf5a56e57a543ebb8f585074adf09383e99d7b062dac92eab1f4d57f/jiter-0.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:36ee6e69027396664e59995b9a635a947a5304ee9837279584a0bb8145c8f6b8", size = 183610, upload-time = "2026-09-12T15:12:10.374Z" }, + { url = "https://files.pythonhosted.org/packages/40/eb/2c4a8075ed5ea02b56911e9375d4c8d7784572ff4af32e5a99ae0d071044/jiter-0.17.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1b18434638228c0c184281609bf3d9459026a0f1ea48fb76c205e3ef72069caa", size = 290991, upload-time = "2026-09-12T15:12:11.641Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b1/34bfa29599d420423baac6ff7cada6674fe63d5a7a2ccb3900b904678783/jiter-0.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ec89771f4272b989487a6364e519db6bbaba323e8bbf949ac89a45ea9c18b7a3", size = 321425, upload-time = "2026-09-12T15:12:13.855Z" }, + { url = "https://files.pythonhosted.org/packages/11/71/a5ac64a62a04aebd556afadab14a6b730001e16df87266ded943a100a1d9/jiter-0.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e3f052c671d5f425cca5ea5901cf11a831369fba4a55a3862cab93c323b4c3b", size = 343138, upload-time = "2026-09-12T15:12:15.046Z" }, + { url = "https://files.pythonhosted.org/packages/01/dd/f761e320ea473314cb68612bc6a435393464dbd198051399b36848b4ebf3/jiter-0.17.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785a216bbaf8f15fc974e964ced7322cd3d774bb0e86949edd78c6bffd6ba35b", size = 325805, upload-time = "2026-09-12T15:12:16.506Z" }, + { url = "https://files.pythonhosted.org/packages/19/1a/27d8e40f0fb29bbc7a5adf30907144396a115dbe93d5d8976c054a6dfe96/jiter-0.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d85c558c9f8532bba287a990ac63767c7daf756f0d8c030219f62499b1fa228a", size = 340230, upload-time = "2026-09-12T15:12:17.682Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c0/30bcde78a28155461f965d16b7aca4ffca6d17494d905f7a0bb072e6c64e/jiter-0.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c23849235d2142ce444b2b8c6eceee9f82f4cc0bd5c9081602e4155c6197807", size = 351343, upload-time = "2026-09-12T15:12:19.337Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/91420b156315ae22732f5ee1a7b5725a030aab9dc8fd7dcdacfb4aa588d3/jiter-0.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58df29268a95e910f17db7ec9178eb7f15aa8619aaca3575275c4e6b3f4fe4c5", size = 344990, upload-time = "2026-09-12T15:12:20.705Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a2/ae6d5672644cc11127970277c9aeb0fa6fae376845587f5b0a8e8828167c/jiter-0.17.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:a277f97eba7d66b1ee27eb5dab5b774ff46a10c78d89a1d3dcce04ce1357c8ca", size = 328624, upload-time = "2026-09-12T15:12:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/04/62/45cb1162f6aa586536e4a973fc339d72dc6b08cca030d70a838a307aa778/jiter-0.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe15ddf316f1f1f643347d3a474e74ce61880c79a11ec5dca53df20c071bd3e8", size = 334731, upload-time = "2026-09-12T15:12:25.229Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/45c1574b644da7deda0b7591c349520dcf83ce45b24d7ca19922dab1fc27/jiter-0.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:02adebb7ce6413c44d40af9ad59d1c1cd79630ccdcb6f7bdd2d461e48c03d8f9", size = 483649, upload-time = "2026-09-12T15:12:27.557Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d3/ebea1ecb5b241c519f192b30215c79a8e47f42f1621acbcd6f8830728416/jiter-0.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:55d0e0e613a3f9ad600cf436e0e2b8057d1b52bcf1d91b2d36ac53451231e6a8", size = 520758, upload-time = "2026-09-12T15:12:28.99Z" }, + { url = "https://files.pythonhosted.org/packages/64/e6/682b641ff0765ea9bdc349dbc7d223de5c8af8ec1abda0db3406992f92fe/jiter-0.17.0-cp313-cp313-win32.whl", hash = "sha256:2c45ad7c973ef33fe5114a953377b35a95240f4542c0724d9f781e47dc24bac7", size = 184334, upload-time = "2026-09-12T15:12:30.813Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d2/9a49aac2b27af4cc5015e368c0cc3588491a532f717a668ffce1f1ac57da/jiter-0.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:a3cebb1fe4a1abb00465f3f8a17e09112603e8b7c59e5c3adbcd9f7815a64acd", size = 226601, upload-time = "2026-09-12T15:12:32.096Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ce/9a43e9f614608eafa78de22aedcff54cd21324467b5d442d5c9b00244145/jiter-0.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:96b8b0c6dc5d78682f54a450785e075aa929cde768304cad363cd4efba5a82ac", size = 183103, upload-time = "2026-09-12T15:12:34.396Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/23065f8e2c7a4c372c1b6f6622e4cfab4dc786cb5150052b1527e6a6a840/jiter-0.17.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:00d783a779c5664e16dbad5e3a3c3a75e128b07dd5f4765159658d9210a50ca5", size = 292210, upload-time = "2026-09-12T15:12:35.613Z" }, + { url = "https://files.pythonhosted.org/packages/ea/81/67b58647560bc82a4490d722caa8561d7a86a9f45d4fa620b7e5fe282c7a/jiter-0.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0619d806e260ecf0c2a64521942c94af5d547c9ec99b55ae4f51b538b5576a76", size = 321512, upload-time = "2026-09-12T15:12:36.907Z" }, + { url = "https://files.pythonhosted.org/packages/c7/07/6658359a25f55927f7f8bf0e16465dee2ccd0b2a1a5208acc0df8972e074/jiter-0.17.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc0288ce39190ee33fe6e4ec73161eed34e7e2da509b525546ca061778d62b64", size = 343897, upload-time = "2026-09-12T15:12:38.189Z" }, + { url = "https://files.pythonhosted.org/packages/46/04/5d50a9f0319cbdc37fd53c27f8c313d46afc34f1b048219ae6d8ea068da4/jiter-0.17.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a52a430d04225ffde633e6840bf2381d34c019ff98526b5929755b9052fb199", size = 326519, upload-time = "2026-09-12T15:12:39.532Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c7/d02517832b29eb8275fdd0f4ce0f17b80f58cc4c3ebecd4d9ace990d633d/jiter-0.17.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37f33d327900bf2879613b3363fd48df97b4232d0c41f54bcf2e790c2fc40a71", size = 341369, upload-time = "2026-09-12T15:12:41.486Z" }, + { url = "https://files.pythonhosted.org/packages/3b/07/499b5f5603501cdd93a73a6a176dfad9c96555a3ae58ca9f8e3acba63dc9/jiter-0.17.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cf564d43c4388149ca58ee571d0f5ccf875e20d1fd4662fd94cc0d1ea3b10ef", size = 352160, upload-time = "2026-09-12T15:12:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/f5/75/b04013c7743269d4533ef4e746fc0ed678a143968dd7448658e3f51daad2/jiter-0.17.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:523c499235fb65add25d4bb01b1c4709ce695efdc7deb6c0a7bc515b5c44e0fb", size = 345018, upload-time = "2026-09-12T15:12:44.192Z" }, + { url = "https://files.pythonhosted.org/packages/1d/96/cbb6fd1e42a77c8412ec4643db95059b30cdfc635e387cc9193e098ce268/jiter-0.17.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:455e4ab35cb2a4a91a8404e08fd3c621bae433922e59bf1c494fe20a426b013b", size = 329244, upload-time = "2026-09-12T15:12:45.491Z" }, + { url = "https://files.pythonhosted.org/packages/15/67/d3be402f398566a379bf40ae65be5c3505b14d9e95e0802a597ddde7ddee/jiter-0.17.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6871973bfbd4408f7f1c632b30bbb5bbd9671c1bc8650af6823e24b7be13709b", size = 335693, upload-time = "2026-09-12T15:12:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8d/98e2c4130b93d64f1d67c89060b928d04102549bf05e64451c9e6024f9ca/jiter-0.17.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:77f6aac0137309b31448c1bdcda4c6c77077664a6d018ece8d94019c68a5a5b9", size = 484329, upload-time = "2026-09-12T15:12:48.361Z" }, + { url = "https://files.pythonhosted.org/packages/78/5e/8da91e49f0fbca37c3489fb4cf3ad6676d4965f00ae5468bca3a2513737a/jiter-0.17.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:93946d89fa04d5ba64dd323a8dd8d901676cb8a3c81d99ae4f6c051a9b4c3f2f", size = 521358, upload-time = "2026-09-12T15:12:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/be/21/5388684a5a38af3557cd9c2424b9827c71809cff24373c75ef9d0d3dfba9/jiter-0.17.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:70f19a2ca8429f91e82eeffb2f51cb87bc2d6e953b009b91a92d29c3a16ccb03", size = 110459, upload-time = "2026-09-12T15:12:51.747Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ad/58b3a93525d2ffca7f54d9dee441381990082bd1172fbeb8d6a3f72a4dc3/jiter-0.17.0-cp314-cp314-win32.whl", hash = "sha256:71dbd74314c5df52a1bccf7b8bca46d14e943af7a2012e73b23f49977ef194c8", size = 185043, upload-time = "2026-09-12T15:12:54.477Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4a/1aa520eb6c359b262c14ff995ca7283837208ddfb1202082ce9d73cf214d/jiter-0.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:ac3c6ee3264d6f5c44c617f90bc7e8b9e1587e7d6708c9d8f811cb65582ee312", size = 227163, upload-time = "2026-09-12T15:12:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e4/5997f648794bd9b499491d0ff480b096cc9a9c65bdba29f57568e6aa1705/jiter-0.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:6219adaf59711ba7063a52496e8ec6d3fa3e209d7827d83eee3b2abc780a1744", size = 183505, upload-time = "2026-09-12T15:12:58.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/84a5ec271d09f7590b6073af5ee4abb44eab4ccace453b7e2c5ce45234ca/jiter-0.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:59bddbe6f9ffecc68d641e1e2d619ce64cf8a9e9eeb74e5c518f74fc87abf1b0", size = 321527, upload-time = "2026-09-12T15:12:59.394Z" }, + { url = "https://files.pythonhosted.org/packages/39/71/9e1fd0045f5920b4c36be35c3f0f0dfd123668684f8ad352619d7aa44183/jiter-0.17.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cb41cd1432f1dc19a231cf70b54d42b2c9f05085155859263fce06fa4d41388", size = 340865, upload-time = "2026-09-12T15:13:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2b/14627fd2bc377f3dd09491bcace6b90e34b4d7fea2f1f3295031ff91f528/jiter-0.17.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd7790aa79c8b518e512ebcdfce9f11d8ef5f30efd43720c8a19a548b39fa489", size = 325412, upload-time = "2026-09-12T15:13:02.152Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/8d5808f7bf0f456bde79e6393587183a0cee5f83d179fe1f7f1eff2ba067/jiter-0.17.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbbfe4e3c21c8166980cddc5bee1a315df082454f007947dfb6fb73800768165", size = 340473, upload-time = "2026-09-12T15:13:03.485Z" }, + { url = "https://files.pythonhosted.org/packages/4f/da/1d8c7c6c4ae6b2423b94a81b6b907d37b28f87664e077427b531bf1b5313/jiter-0.17.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c286860abfe8b100cac1c02e225e5776eb9216edd71ba17cdb237da4af32bc9", size = 350757, upload-time = "2026-09-12T15:13:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/eb/96/c1813dcca15c5a370145a448aaea7d1f83f6f0228a5f1130e79340ee385f/jiter-0.17.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f753eb70b1474a29e635e7542ff7312e6d6b951e0b25e8a2e8c34eeb1ddcd478", size = 345203, upload-time = "2026-09-12T15:13:06.131Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f7/fc61cbcf2992d169ede13648fc3fd8e2d3171a3669dde43cd4db556549ac/jiter-0.17.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eae86b1f027031e39db2e0e9c4842221edb7b8cd474d23f87a79b3bd4b651768", size = 328322, upload-time = "2026-09-12T15:13:07.392Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/46418a3abbdffb7dc41b314200360f24f75faaeb35573e81c92de322cce9/jiter-0.17.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bf350452a43173e69e1fc74847c57a60e3d7515807287f29849baa2a85d8718", size = 336570, upload-time = "2026-09-12T15:13:08.666Z" }, + { url = "https://files.pythonhosted.org/packages/f0/28/b8a55b949be6306df8888e365a8df05441de8a7b11289f6957004302e41e/jiter-0.17.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:da139721f4b7cafdbff580a4f511ea24cb91f4909330c6b926a1ca53836c0a59", size = 482879, upload-time = "2026-09-12T15:13:10.037Z" }, + { url = "https://files.pythonhosted.org/packages/75/3b/21d0afa53ba0680962c39f3eb95ed2946f8793369ed44b0c82b490723081/jiter-0.17.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8079849db9a1371bfd90bad088458a8fb836261879df2233cc9632464ecf64e1", size = 520406, upload-time = "2026-09-12T15:13:11.456Z" }, + { url = "https://files.pythonhosted.org/packages/ef/03/bcbaf8b6b9ea23c2c074411f8ecfbb02d820abac5d0cb8f4e280209174a2/jiter-0.17.0-cp314-cp314t-win32.whl", hash = "sha256:8f770b0c77e5fac482e1ba03ca1a7e18286bfb213d749932a00a7e4cd5de5e06", size = 184434, upload-time = "2026-09-12T15:13:13.037Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b5/5d6ce2c93ef6fe1241b37a9005547f9b6d58db1f07f39fe95807d4b98f51/jiter-0.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c4289293e5278d9314b00f15c37f2120fa51d3d68565292e715524c750e775a9", size = 227392, upload-time = "2026-09-12T15:13:14.933Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4b/1e52baf90187606e33a7b8cfa8f96f5829acd7f01870077eb01059ab76d0/jiter-0.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4dfbfe5a6e1e80a7082af559f66386405025ec278833e0c649f69cbc6e1004cc", size = 182776, upload-time = "2026-09-12T15:13:16.239Z" }, + { url = "https://files.pythonhosted.org/packages/05/fc/efe3ac75564ab10f53517958f5ccdc231fc7334af66c76776cb554a88967/jiter-0.17.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:84963d3f395ef5e9a32ce47155e08a7962fa292c159a10cb98b931cef1416925", size = 292143, upload-time = "2026-09-12T15:13:17.502Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4c/46982118d91f9ffe9714319d21ec4f98d9b7e0cfd9062826c524a54de24e/jiter-0.17.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ffa0380ad091de7d3fc33e17a97ff479851ee18a0a2a3ee56ff3215cdc886656", size = 321341, upload-time = "2026-09-12T15:13:19.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/12/9b1ac6ecc6307049913db54839ddba1c11c1ef72c5a8bbb5514bc3b50d1b/jiter-0.17.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755079792868ce5d4938e83b91a0939b34fb858a1ca65a104f2d771bea57faa1", size = 344383, upload-time = "2026-09-12T15:13:20.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b6/527cc72af836d824e9d4d666e64f0a1ca7eafd662a8da9657b78592172ba/jiter-0.17.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3bf4dc2b84a464117fb097d15a25c58d100d2692888e3b0d92df5b48ed16b7c0", size = 326841, upload-time = "2026-09-12T15:13:21.83Z" }, + { url = "https://files.pythonhosted.org/packages/d1/41/567f98617e88005b249503b933803f633ec6ba2d427cf4cc35e5c832125c/jiter-0.17.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02a360707033d8cef53f7f3480817a1489177a259ec6ec01e98c37e0b922ddca", size = 341354, upload-time = "2026-09-12T15:13:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/40/da/b29cda895b785f7d426e224638a885b6145a08ce853b381f34afe3e88c5d/jiter-0.17.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:300ce01ab0215e3dea4d00090143c909aedc65c0f809b3c07983e1d038f291b9", size = 351985, upload-time = "2026-09-12T15:13:26.526Z" }, + { url = "https://files.pythonhosted.org/packages/f7/5c/8a73829e7389e72ea298a450f2b3cb58e71a3e464b45f6d8753740f1c4f5/jiter-0.17.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746243a080b4ca790b8499af3d7cf9825d5f5987933950cd818e767ee353d826", size = 346052, upload-time = "2026-09-12T15:13:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2f/98d6001026932c095ba440925570123043bed29f5ff56158dfe729a9e81b/jiter-0.17.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:b550585523339b71cb852b811aae49d08d7601ad8ffe9f5dc1562f4c3d22fd87", size = 329159, upload-time = "2026-09-12T15:13:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/94/2e/708dc1d2678f092c31c12754e860cd8353e6a85ecbdb1010157edca0da9e/jiter-0.17.0-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0239520085cac678e77a606fd7e3f1c60c371d719790c5e3807388d3da4354c2", size = 336001, upload-time = "2026-09-12T15:13:32.846Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/75a5ae38862f4eaf0fe2f8a9fbf6484c4890df04c06dcdffc45e36bca61a/jiter-0.17.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:eb2295da7c3769f6719b227a237aa6a5cfa6550e478bc838001b592c57e16575", size = 484281, upload-time = "2026-09-12T15:13:35.333Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/6636fae811c27c7f93e1b11fb5800de6a5c9e4269a27cf718e0b31218ad1/jiter-0.17.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:e088612ff90ebc9247e1a43074b72835804261c47e6a6c01cb3ddcb55360d688", size = 521300, upload-time = "2026-09-12T15:13:37.101Z" }, + { url = "https://files.pythonhosted.org/packages/61/aa/12df7e0b0b1a2602e3d5a5a7104d7d9700f254b400f134a9b50955c4d231/jiter-0.17.0-cp315-cp315-win32.whl", hash = "sha256:0b52d52035b3907c5b1f6277857b29c1cbfc965e24e0f27330dbed83edb591ec", size = 185138, upload-time = "2026-09-12T15:13:38.901Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ec/3dd2e495032cddde05723c1f4c743b67a23e55d2af244692a7f58f0cdae3/jiter-0.17.0-cp315-cp315-win_amd64.whl", hash = "sha256:10f5558eed511b830488003449d942bd75829ad6257dc58cb9a03e596a7777b1", size = 226950, upload-time = "2026-09-12T15:13:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c7/ef85704e0a57e9cadb2babc05f6d7c5df4a1c75da1a6ee31e1986b0099a5/jiter-0.17.0-cp315-cp315-win_arm64.whl", hash = "sha256:fa13acf1046f95df808c64b1310705e143fab87aee73ae00cc42d640867fd2c1", size = 183618, upload-time = "2026-09-12T15:13:41.432Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9a/a4b348349de68762b58d6713973d363ad80a1c741d0bf8def7975f0ecb26/jiter-0.17.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:af2f7501580f274b63c4b2283bc425f5df7edf06ae5b171e5f87d912ff359a20", size = 321155, upload-time = "2026-09-12T15:13:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/c1/70/aebd6d0b5f0677de3a3d0bdc4a05fac949b97c4ede454c8809f180ac7b17/jiter-0.17.0-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10c5349312e5cb02b7a21e123a57665afa895953f05bf252a9dd4c13a572b7ab", size = 340985, upload-time = "2026-09-12T15:13:44.115Z" }, + { url = "https://files.pythonhosted.org/packages/a7/82/4c3b49796b5eb62f3f5046f957683f4ba0135fe1a60957c11180512460df/jiter-0.17.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:86f3f9343a288eb85a81ef20a752b2f84564296636db54a9fff0b5c8deaf1df2", size = 325670, upload-time = "2026-09-12T15:13:45.901Z" }, + { url = "https://files.pythonhosted.org/packages/bc/43/f6341ecb4872202a4ef150486fcee0e1ace4aa3da39b71b82061452cdd3a/jiter-0.17.0-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4607ec7d93355fbc25b8dc5189153cf21d66063b9f9cd04dd2774e6e783f9b6a", size = 340339, upload-time = "2026-09-12T15:13:47.442Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c4/bc2c86e08fa065e03cb2fbc53b367c3640a7d257ef9d877b29118ea636b7/jiter-0.17.0-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10cd64a5720ad7f809ac5466ff1705813f1b6b510f195a73acafba0ac0e1f675", size = 350705, upload-time = "2026-09-12T15:13:48.848Z" }, + { url = "https://files.pythonhosted.org/packages/9d/67/91f12aa111cca6e3a197c3e36bf60a034bf9f122f6d41112a639e44217d8/jiter-0.17.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe9f61bb30174d2f5c8396445c360c96c44e78164d0815dfe627ccf57849574", size = 345011, upload-time = "2026-09-12T15:13:50.215Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cb/9f5556e8f6ec89755fb5a709d8eb8270c9a324e31079eda0dfbeca451b6e/jiter-0.17.0-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:370d8fe5bf201dc6925e8a84c81ac7291f74d9fd1778234fc79d517064a5c76b", size = 328268, upload-time = "2026-09-12T15:13:51.809Z" }, + { url = "https://files.pythonhosted.org/packages/22/98/153f20680fb75781a490fb849940e2b00f95035c7aa054df592f36ed33fc/jiter-0.17.0-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b303d88e6a0bda789ec4b7801c7bad68e27230ba1fe4baffc756d1fbd32dc9d", size = 337024, upload-time = "2026-09-12T15:13:53.095Z" }, + { url = "https://files.pythonhosted.org/packages/af/59/b16c9be3a5035df4466cc72e888188c027562de90a723d290ab6814cb9d4/jiter-0.17.0-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:30793a24a31e968969757c9e08d830cbb15a2cd3c4959b4498b38f4b1c2258eb", size = 482766, upload-time = "2026-09-12T15:13:55.713Z" }, + { url = "https://files.pythonhosted.org/packages/d0/55/667dea313094024bef082175d6bfe8976f90d1c00c926af9df1d8e0eab48/jiter-0.17.0-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:686c93d86f2b426c803024b805bd161a6cd10e9627c23e901640eab646c0ad8a", size = 520367, upload-time = "2026-09-12T15:13:57.674Z" }, + { url = "https://files.pythonhosted.org/packages/21/e3/4b1a43501fb9ed17b01d137e380cb0e8fdcb39a254ce31aa2ab95bc861ac/jiter-0.17.0-cp315-cp315t-win32.whl", hash = "sha256:86d703d9faa1ffc8ae4e9de0fa007712ed2171b5c0d93811a8e2e105ac729b0d", size = 184603, upload-time = "2026-09-12T15:13:59.27Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f2/b8ee0372b6ebdf1bde5cc44495d5291d17f961065f5b48f8616cc67cac2e/jiter-0.17.0-cp315-cp315t-win_amd64.whl", hash = "sha256:42b0260445251b1bc520a63baa94a32d88e0f931fba234f1764db7feb7c72174", size = 227936, upload-time = "2026-09-12T15:14:00.472Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/923a1215daba959aed8355973315cb3f81f53e0d01c5b211870a27b41f45/jiter-0.17.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d47687806f9c54c84ea38733507081337922beca90ce819c7d852dd485bc0f23", size = 182977, upload-time = "2026-09-12T15:14:01.799Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3b/05a917204413e2e09906dfa35240c1021227aeb56c7305abea9562c598b4/jiter-0.17.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:eaba834b72d573547b9d966465b3394b749d5e14208cc70acb63aca37619ab33", size = 288143, upload-time = "2026-09-12T15:14:02.998Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e1/a1cd3c0cf8f79945939e4f8caae9990529f67d7f77f671769f73956329b9/jiter-0.17.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:51e1519d676a9f14dad9c2a411170d43b022ddb7989562df4e849b261ce127b2", size = 281847, upload-time = "2026-09-12T15:14:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/72/b4/9b797679e09f4a46c32986aeb3670bd9bc562fc0b373c7a0ee5c5dce1206/jiter-0.17.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ce4feb52493e3513335b2accdcd75605652e4632772d3c8c2f7b86954d7f39", size = 304797, upload-time = "2026-09-12T15:14:05.733Z" }, + { url = "https://files.pythonhosted.org/packages/25/4a/0d77415b27a00d970e4e710f7c1de62e96a11c4cab3ed1add0015af04626/jiter-0.17.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29f49b325e0234e4ad9ecca5b861ffbd09b95ccac9bd46fa55841b6e56eea5fe", size = 307815, upload-time = "2026-09-12T15:14:07.105Z" }, + { url = "https://files.pythonhosted.org/packages/17/31/4bb27f54333d3b9ef1e5bd3312dc0b4bbe59c68bb0885fdb40583a6b1567/jiter-0.17.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:454c4997d73cc466c71fd565d91e603b0274e48ea0c6b0b7a7aee6967e4ceb7c", size = 288415, upload-time = "2026-09-12T15:14:08.455Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/879570ecf82574eaea77c5eb10309f4b630dece5f2a556e9814a90ba3f2d/jiter-0.17.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:40d2c240f8f80b5b0f201b29f0ae129c81448c60c772227a41747b5e0026f6a2", size = 279113, upload-time = "2026-09-12T15:14:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/77/7a/1f0b8a35fbd079a4f1752c31a15dc99cf277f863747c459be0af39e900e5/jiter-0.17.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e05f5adbf68c4bd11e1610f394034d984152988e84be6f8314235ce6f2139e5", size = 303708, upload-time = "2026-09-12T15:14:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8b/d76219ebdbcf3d4209d9d21a0810db4c8d0a6f88e3ee87d30bdea4e90d30/jiter-0.17.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c0bf24c72fd0491405dce5d40194f2070e9021ce648c1a1d46234b93d848ff", size = 307147, upload-time = "2026-09-12T15:14:12.897Z" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/iniconfig/2.3.0/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "litellm" +version = "1.106.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "boto3" }, + { name = "click" }, + { name = "fastuuid" }, + { name = "filelock" }, + { name = "httpx", extra = ["http2"] }, + { name = "huggingface-hub" }, + { name = "importlib-metadata" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "openai" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "tiktoken" }, + { name = "tokenizers" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/4b/0f42182e4a3f957e8b5d8bdc2522779092da973bef7a3492c8e6a3409764/litellm-1.106.2.tar.gz", hash = "sha256:9f0e770cb007c8c6e78917ad7b5c4f059f7bdc4a3606f854831780ea53fd9e2d", size = 21431422, upload-time = "2026-10-21T10:44:51.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/47/1038af80d9831972fae8712a890c30252f51c514c092d83e0f1330bb1fa1/litellm-1.106.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bf6f13445bb59d8d02131bbc1fb57161d1ae629bec65dc66f9add4c09fb6994a", size = 36283395, upload-time = "2026-10-21T10:44:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/51/fc/15e44b2a222dfda71e0aff107fccfb49b06a4695fae0eb6867aff84f7e1d/litellm-1.106.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:cc778d01ed65a8dd57d5211d581ec237e06df239a09c3d573ace33e0feea76c9", size = 35263059, upload-time = "2026-10-21T10:44:32.664Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/f21d7fd8f7ffa6ebacefbedfb6268430e2b55ce9b2f33a36ac0295ccd500/litellm-1.106.2-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:34bfdc26c2c075726eda59ec0675e2a8a67b6e59a03980def7ef7651b849141a", size = 35827639, upload-time = "2026-10-21T10:44:35.553Z" }, + { url = "https://files.pythonhosted.org/packages/6b/30/4a38c22181ef7169b6c044b8294f2e5b06236aeeccfbe57c2f0e85a51bfe/litellm-1.106.2-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb1a2a1e55a4a8350ae2167525dc0d06f59b078cd685fb3e0f5419f02da5420e", size = 36789555, upload-time = "2026-10-21T10:44:38.879Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/e7ca210ad574f5321c162f2b27f8674c260e0857323a9991c4d3765ce8f1/litellm-1.106.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ce18971e2bb2aa58ef8efae1cd12da1248e1c2e5ae3cf5903bfb9d563bb34d9d", size = 35914468, upload-time = "2026-10-21T10:44:42.268Z" }, + { url = "https://files.pythonhosted.org/packages/22/03/95518dfb58e29c45ab40a7bc3d1d76509d09011172c380145fc9e900a214/litellm-1.106.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:32e567ee53c812e36f79158ec280c8850973f84a9e5fdef50d31ca09306d148c", size = 36901860, upload-time = "2026-10-21T10:44:45.807Z" }, + { url = "https://files.pythonhosted.org/packages/48/7a/a17de89184b5365980c3eb7d58176ae5a7e34538e56b942c833ff7f523b9/litellm-1.106.2-cp310-abi3-win_amd64.whl", hash = "sha256:836ab19110504ad7ba71d8ce73af6b9d1cf1dc6ffda2b49ebc1a51e348f754bc", size = 37619702, upload-time = "2026-10-21T10:44:48.564Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/9b/e422a865e1d5d57d0e509b4e0bf1c1a70a7f6382c29a5aa428df994c8bc8/markupsafe-3.0.4.tar.gz", hash = "sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6", size = 153777, upload-time = "2026-10-02T23:07:22.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/aa/9a65962e364bf19745f6bec7bde398fb1f5ca53ad2e734258e6198cc32ba/markupsafe-3.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd8ea6ebee7aedbf7c749fa80521d9ccf1ba473e0d1e14805caafbaad281c889", size = 11521, upload-time = "2026-10-02T23:04:20.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/36/927999a34b7d1def6957de89153d327fedc4030061940b5a468584e6c5a8/markupsafe-3.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dff05cb7016dff1e9fd68f4122c127b65dfc59de5306cfb7ad92f956f230bee2", size = 12066, upload-time = "2026-10-02T23:04:21.275Z" }, + { url = "https://files.pythonhosted.org/packages/17/54/69e7b0db9bd687bfd83451eed5666384d67cfed3be062bfc59a06a15474e/markupsafe-3.0.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf63c214fe879a65e69a386f915e36104fc84254ab141240f8854602d8e0be2a", size = 22081, upload-time = "2026-10-02T23:04:22.221Z" }, + { url = "https://files.pythonhosted.org/packages/bd/14/f6f5c97903f7d2db76bbfaced31509a47d360a103b3aaf4849f6536591ec/markupsafe-3.0.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2a6ef68ae94aed8721934072b27a3b654ea2100b97e4ab864cf1489c90926fbc", size = 26079, upload-time = "2026-10-02T23:04:23.346Z" }, + { url = "https://files.pythonhosted.org/packages/d0/04/c3cc9b75f94f8b54d7e503c44cebd4b4a115ec1d6f1b996b999807daba99/markupsafe-3.0.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd9f8797427910198f95bced71ddfed61130d7e349213bfb8466c9c99e2c46a8", size = 22611, upload-time = "2026-10-02T23:04:24.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/26/c4708ed3b0f08e8e6d7cbce3314ac130cb5352d1f62951b6fef7878f2b1e/markupsafe-3.0.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1aca03ede943eb80ab3d63bb082c84b7aab85ea83bd0fd0c200260945fb49d9", size = 20712, upload-time = "2026-10-02T23:04:25.386Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b9/f3894d6aae3d7a52c9363317f4baf2fcadc052163d6dbc871266e32639ed/markupsafe-3.0.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0764a13d34cae40db7bbf3a09b7e9b491bf4603e20b263a7a9d6b8e324975d0a", size = 20742, upload-time = "2026-10-02T23:04:26.403Z" }, + { url = "https://files.pythonhosted.org/packages/59/7c/8e248ddbfe286ab6bddb462bdac0851cbb1510d2cbbb815a415fcb0511af/markupsafe-3.0.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9388003072b95f2f1e3fd908604194d653ba21330d811961a78b7da1a77e9e36", size = 21560, upload-time = "2026-10-02T23:04:27.522Z" }, + { url = "https://files.pythonhosted.org/packages/df/26/2353fef7d4bcf2b18e16bad81979fcecff2915156ca6882447917205b8e2/markupsafe-3.0.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:8698d70a8081ee8c090dbb394768b5789a1da8b131b5499f89d071dd3cfaf6be", size = 20973, upload-time = "2026-10-02T23:04:28.573Z" }, + { url = "https://files.pythonhosted.org/packages/67/6f/a9561d98d9a6ee3494b0b970a1c766e58bab128cc84841d56ec009456dbd/markupsafe-3.0.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf053da3c97a4bc5ecfbb218cdd2983febd91c617be8367d139882aa11e490aa", size = 22036, upload-time = "2026-10-02T23:04:29.568Z" }, + { url = "https://files.pythonhosted.org/packages/55/83/6217df9192eca95af3ff0cad955c9854a93fa43b288cf7411ae24713eb52/markupsafe-3.0.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:9438a2648b2195980cb2dd8e53ed7b8df91319e2d0b70ae61a9e1d1bc8d3bec9", size = 20314, upload-time = "2026-10-02T23:04:30.881Z" }, + { url = "https://files.pythonhosted.org/packages/35/7c/9cd8081dae70e17fdab3558121fa618d459c7950c8abd8fc10fc024486d7/markupsafe-3.0.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:88d59b473bfb03259722600839af9bbd7fa13a2eb514beefeedb95997882f69a", size = 20780, upload-time = "2026-10-02T23:04:31.975Z" }, + { url = "https://files.pythonhosted.org/packages/44/37/c5f2f45f4d8f0c24e4af7b9dc711640c888044cbf5f7e2ba2c0bb74cbb55/markupsafe-3.0.4-cp310-cp310-win32.whl", hash = "sha256:4a540e2d3192792fc84eced57bef37851ccb2b41f73291bb17408eea77bcd278", size = 14096, upload-time = "2026-10-02T23:04:33.129Z" }, + { url = "https://files.pythonhosted.org/packages/50/50/394a1c61c9b9972cb4f76875c332af7109bd39bdad09d3a0ec5327a339dc/markupsafe-3.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:5c22873ad1f0532ba40fa1727f3c0fc1bbbaab6d373d4cbe3f0dc74b2e2521c7", size = 14329, upload-time = "2026-10-02T23:04:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9a/86d03d2f32ba41a57baa124a7bcb76d0a6d39b1622e9bd4d550074e8b61a/markupsafe-3.0.4-cp310-cp310-win_arm64.whl", hash = "sha256:3d23795802fc8bd72534836d64489bbf0f67c088959091bdb22e10735a5107bf", size = 14196, upload-time = "2026-10-02T23:04:35.201Z" }, + { url = "https://files.pythonhosted.org/packages/32/55/18dbb4778b30ada5ce071608503cc3edc9e14e13d868c17a6d178fc30f7a/markupsafe-3.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e25feb9e330b63edb0278a0acdf85e50d0cb0fbf49c3084abbe4e24ae195346", size = 11520, upload-time = "2026-10-02T23:04:36.299Z" }, + { url = "https://files.pythonhosted.org/packages/6c/14/0b05f79b4733e264a18d08fe08fa1df7347630ff32a6cb82180d9dccec55/markupsafe-3.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3391b2188d18737cb2fa147028b1096236eaa7e156446c650a489fa2cadc91", size = 12063, upload-time = "2026-10-02T23:04:37.291Z" }, + { url = "https://files.pythonhosted.org/packages/ca/3a/63ba10b6c1463216b3e4df669a9f0e5a3b0c3071557d2e8229e3968c79fb/markupsafe-3.0.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:849dd2bb0e5e4ab2b71c7191726a4a8d5aa8a610daa584728cbee0b710ddc4ef", size = 24321, upload-time = "2026-10-02T23:04:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2e/5f015261b76ad633d187ef6f388b413aedd64a8773c4df59e530a0be5525/markupsafe-3.0.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:befb4158af32106b9a93db8d6d1d1cbbd418c0d5aca0cabb7b1780abf0c89169", size = 28845, upload-time = "2026-10-02T23:04:39.295Z" }, + { url = "https://files.pythonhosted.org/packages/33/cf/26e594b26be40c2f1fec63ccf8a8b99d0335a5b2cbe84835c7d82a994375/markupsafe-3.0.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71f88e749ea29f67f21f3b36433c1dc54c7729ed2a6d9e2da2e0d9e0d7b224eb", size = 24701, upload-time = "2026-10-02T23:04:40.348Z" }, + { url = "https://files.pythonhosted.org/packages/81/a5/a513b76c139a3915b43404324e55c0b7979ae4f0d39eb6f075b0282e90a8/markupsafe-3.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6da83a088f8ef93b2d483a8232a4dbf4d69d3d8496b568a03c56becac43e1808", size = 22961, upload-time = "2026-10-02T23:04:41.372Z" }, + { url = "https://files.pythonhosted.org/packages/46/cf/4c66192c100b4542bcbe392ae06696b670f66927be3ac38a213234778ff9/markupsafe-3.0.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f0fac8b13d14bb06c68195f849371924ae53dd7b1c00fed24650f704383b692", size = 21920, upload-time = "2026-10-02T23:04:42.429Z" }, + { url = "https://files.pythonhosted.org/packages/cb/17/ac3662678bfbad649893117ada2ba44dc30bf56884e84f13154a792b10f1/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4a7cdc2a420ca01058182da4253329764d4bfa055564d1eced90e6ba1e8b1d3d", size = 23726, upload-time = "2026-10-02T23:04:43.531Z" }, + { url = "https://files.pythonhosted.org/packages/f7/af/fe47cee339180a69ebca3c57fb3483d0f5cbd1e8337d1871fb1d9c1aebee/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:83b3944fea42a8400edf92fd1770fb8d0d4f7de651353bd2d8525a92dba69a21", size = 22539, upload-time = "2026-10-02T23:04:44.618Z" }, + { url = "https://files.pythonhosted.org/packages/12/32/d55440ba140442800e02d799c9cb5ab597bf6ebdb1177b5ea39a11f797bd/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8138eb83940ec7299024d92d4dee45f601b9e6c5ffde9d25f4e35e326203c707", size = 23963, upload-time = "2026-10-02T23:04:45.656Z" }, + { url = "https://files.pythonhosted.org/packages/50/9d/9c86042cb364c2ad4c971e6d1247929effd25f714cd7ee11b05e6316445b/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:811d02d5122171c1941357efd8f9bf4ffe907b7f0a1a4e729a880e4be3f46e3e", size = 21506, upload-time = "2026-10-02T23:04:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/75/ef/5b824f03ba40c3b3652b6272d083d2fc4fcdd440de519a3a39ba2c3e7262/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:50b5bedc9ed8a94fc8857a42ef4f84a81ea88f8d4f05dc8705fb23ee6d8dcca7", size = 22955, upload-time = "2026-10-02T23:04:47.698Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e8/44cfcb5ea40e5e43cec7793ef90704ed0c475280839076c4345757eb8e59/markupsafe-3.0.4-cp311-cp311-win32.whl", hash = "sha256:2e5a7cd7fdd14fcb1ae5d7d8bf23d24fbd1daefd1fbca2580132e1ea75f098b5", size = 14094, upload-time = "2026-10-02T23:04:48.788Z" }, + { url = "https://files.pythonhosted.org/packages/91/89/f2b509f7bf79352e40117824c1070dbeafd4df67031d3fa98165a3134228/markupsafe-3.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:fdb4ca07ab75ffadab4a8b135ad59cdbb3156b99310f3d565370da74a15d6bd3", size = 14328, upload-time = "2026-10-02T23:04:49.801Z" }, + { url = "https://files.pythonhosted.org/packages/2b/5a/ccf22672a0f64dc682306e288f0dabcb06c7a201f3bb6e6cbf86d9e8ad03/markupsafe-3.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:569d65055d367e3dcdf30c3f41119467b73d9ee9faf332bdf40402644f5ac08e", size = 14188, upload-time = "2026-10-02T23:04:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/81/09/4c59d56b8461ae8eb0d8ba34bb25b7e618547044679d58a82ef9b2479fc1/markupsafe-3.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61631e08084be9e21a8967ec3139c7616ed7c5e9368e05c86d1b39562c8a57b6", size = 11658, upload-time = "2026-10-02T23:04:51.876Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f0/d6613774d86fbf6d145751d43c59875e47a6f9f17daee0aef173bd36d90e/markupsafe-3.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0930db9bdc62d22944e10b066448bb65dc9abe9112880c7cab8da54db4284d5f", size = 12050, upload-time = "2026-10-02T23:04:52.931Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f2/8f18e0b806eb13c1f8d07d917a720831ead54253a6dec011fbc78098a6f8/markupsafe-3.0.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a45c3d514f2436064db00d7fc8778d888f0236ebfed649b53d13a59e69ad51b", size = 24363, upload-time = "2026-10-02T23:04:53.895Z" }, + { url = "https://files.pythonhosted.org/packages/60/ce/fa07dbe8a5675558fa36dea033e19995bc783de2dec5f540ccb9030b06aa/markupsafe-3.0.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1e1451fab512d1bcc3dc26988ec1edb0b82c2db909132872cd9356070a6b63df", size = 28525, upload-time = "2026-10-02T23:04:54.905Z" }, + { url = "https://files.pythonhosted.org/packages/85/40/be87c01f3868ec217f8a2015089d71c22c8c5a75324822e5ed1cdd87210d/markupsafe-3.0.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bd3ce56ae2cbae3ba82b683bc425cd7e48d2ed8b10f3e818186b6f5646d9271c", size = 24733, upload-time = "2026-10-02T23:04:56.229Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a7/aeedb5140afa41fc74c225e9184ab96723a6e873b6ee1c9fede7283456d8/markupsafe-3.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e124f974786f831d6043728e38296969d3579db8896fe004682f5758e613581", size = 22985, upload-time = "2026-10-02T23:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/c3/fc/e91352bb08c6a59da3ef0909d457bf95a5f5908fbf151b30a06d9dbcfbb4/markupsafe-3.0.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c02e8f18bdedba082cef725942ac823b9b60656db07f7e265cb31618dfd00d77", size = 22001, upload-time = "2026-10-02T23:04:58.597Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f8/bffee5e7d2a3deb59748a797650a48af7e672025cf641a79344a771ad106/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f098115c247e11d138ab83a28fa0323c77015007ea2df73ba5fd714dfefd67c", size = 23793, upload-time = "2026-10-02T23:04:59.686Z" }, + { url = "https://files.pythonhosted.org/packages/ed/59/b853d6628ecb4d658e1d637224846d5e9bb4adf4f8df97f3be9f29dce2ec/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d5f93ebbeb8032d47e349328ec8662d973d9b05a70b3c35df1f91fe419b84749", size = 22640, upload-time = "2026-10-02T23:05:00.768Z" }, + { url = "https://files.pythonhosted.org/packages/09/b2/1506df394f0f075797c418d0301498f49e43be194e3ffcb49e6fe6ccf022/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:64511c54db4e4987aef4c41923235927428729e8174c5dba488429be70a998ed", size = 24074, upload-time = "2026-10-02T23:05:01.813Z" }, + { url = "https://files.pythonhosted.org/packages/c7/81/5ed69cda630ac69ef60d06c09ba5a7f84ff66a2e28cf986fd5614ab3c6e6/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e1a622f13970d81f95d0c72f9dc090dce9085fccfa4c9f2174377ee32bd15786", size = 21563, upload-time = "2026-10-02T23:05:03.239Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fe/fb1e79be0fea60aa32602ebefc9c35a82bb42b4df157285ab7dfec12341a/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9a7f43c0b202b334cc9184af09bb8f21d3a209e038efaf106936fb69e6b026e", size = 23048, upload-time = "2026-10-02T23:05:04.479Z" }, + { url = "https://files.pythonhosted.org/packages/c8/52/7632a53360671a9b750cdbabaf9cdd89f18b42248b8e4cb42c0b0296e459/markupsafe-3.0.4-cp312-cp312-win32.whl", hash = "sha256:f0ec3b750b59375eab5b0fb2b9254810c00a3375be6d789899f1055a1d556237", size = 14108, upload-time = "2026-10-02T23:05:05.513Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bf/62495e180b7000aaf30000fff849e933f74264638057176cf46852500adc/markupsafe-3.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:11935df9bf455ed0c04eb87bcd720f02b1fe5e02128a9430f23aed6f93336fc7", size = 14303, upload-time = "2026-10-02T23:05:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8e/4c24208776a65878d656996945aacfbfe010d3720d1a98fc0eb8491fc03b/markupsafe-3.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:a4bbd2d87dd233b9fc5812160c3d0ffbe42edc22a26ce0469f58479ede633fe9", size = 14174, upload-time = "2026-10-02T23:05:07.617Z" }, + { url = "https://files.pythonhosted.org/packages/6d/18/4bc5ba32499e87bb2b0ef5b3a9bb9c00a131fa961ddf0be548cb550f548b/markupsafe-3.0.4-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1", size = 12786, upload-time = "2026-10-02T23:05:08.709Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6f/17f0c099bf25f3e31e63cc19244d9f6af861a9a4ab778c203997903cfdd0/markupsafe-3.0.4-cp313-cp313-android_24_x86_64.whl", hash = "sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1", size = 12510, upload-time = "2026-10-02T23:05:09.93Z" }, + { url = "https://files.pythonhosted.org/packages/11/af/1a141081b905036ee904ec4bd945e1f70b4e1b32d33c4e59e8cf1d58b247/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96", size = 11583, upload-time = "2026-10-02T23:05:10.884Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0a/a89385ae590232622a03e091805cff12f24fabe6c11e0e8bae096cece81c/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148", size = 11801, upload-time = "2026-10-02T23:05:11.913Z" }, + { url = "https://files.pythonhosted.org/packages/ed/85/ea548dc013962eb73653124bc595635fbf9e0fa41d1f181a967ccb784dfb/markupsafe-3.0.4-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e", size = 11443, upload-time = "2026-10-02T23:05:12.887Z" }, + { url = "https://files.pythonhosted.org/packages/cc/72/15f2e5ec9cf2eb00d5cdfe968d94e4156a7bd7303832c3f3b2c403a36839/markupsafe-3.0.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248", size = 11665, upload-time = "2026-10-02T23:05:13.829Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e0/4030bea613677e333c8a2c901fd405055f657f9d06acba5b7357984b6ef7/markupsafe-3.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72", size = 12061, upload-time = "2026-10-02T23:05:14.807Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a5/28b76a7449eb702966b88bef599e2360b411fbb3afeee8fe560939be06ec/markupsafe-3.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2", size = 24395, upload-time = "2026-10-02T23:05:15.909Z" }, + { url = "https://files.pythonhosted.org/packages/07/6c/21232811afc3a063b5e934b1ae2efda52f46154ec382f585149c020e61fe/markupsafe-3.0.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85", size = 27183, upload-time = "2026-10-02T23:05:16.976Z" }, + { url = "https://files.pythonhosted.org/packages/14/38/6ccdfa5b59049cb36fb80cbc80aee9cf1fc9bb77d1335ad435f2070b08cf/markupsafe-3.0.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde", size = 24772, upload-time = "2026-10-02T23:05:18.209Z" }, + { url = "https://files.pythonhosted.org/packages/63/e0/cec6865dfe88cb48fedd4b20aed6af5158e41092adcbf3e028bcc6ec2108/markupsafe-3.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6", size = 23017, upload-time = "2026-10-02T23:05:19.286Z" }, + { url = "https://files.pythonhosted.org/packages/ee/76/6ed4940bb7648a9aac457c14f870cfdd5105f139a0fb1f29cd61fafa47d1/markupsafe-3.0.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f", size = 22036, upload-time = "2026-10-02T23:05:20.352Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/ed476226d4fe46a09090a36025bf319296810028df55eb12f1253b540f3a/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39", size = 23786, upload-time = "2026-10-02T23:05:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/9a/35/66ff30450e35ef5fba9ebc930c9411747e537fd9447b65e44f5007e2b84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee", size = 22672, upload-time = "2026-10-02T23:05:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/32/0b/72f45ce4b4efcbca4b80cf1b06703eff0be8d37e82abb78f66c85a7ead1e/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2", size = 24107, upload-time = "2026-10-02T23:05:24.175Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/71776e5fdcba04614b384cc102e8a4198208579d896fd1394cb7cb9aa900/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46", size = 21615, upload-time = "2026-10-02T23:05:25.215Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/801ce02a02e7aee0f784b1ec7843026178f6adeb9c93ac67eb1992a9a84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17", size = 23076, upload-time = "2026-10-02T23:05:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/c43776625428f3bb4a61e8633940400e3efe6409e3c6f5bff26de5e45618/markupsafe-3.0.4-cp313-cp313-win32.whl", hash = "sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0", size = 14118, upload-time = "2026-10-02T23:05:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/6f/36/163da64de88a13db79214ef75fa041be7fa13bdb42261cf5b7484de14bfb/markupsafe-3.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5", size = 14313, upload-time = "2026-10-02T23:05:28.749Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a8/9b662783ffaa1149221432a923cee562f78b9cbbb8baa3df9b3753e63e1e/markupsafe-3.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc", size = 14189, upload-time = "2026-10-02T23:05:29.917Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c3/a944f3b0df22bd129e96915b9f4e98d2eeca6516687d7618304a966c3c74/markupsafe-3.0.4-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed", size = 12787, upload-time = "2026-10-02T23:05:30.971Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d6/a44863f69d88b6c7e27889108f70d47aed259edf89d5df3c5fca1eac87d6/markupsafe-3.0.4-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59", size = 12525, upload-time = "2026-10-02T23:05:32.263Z" }, + { url = "https://files.pythonhosted.org/packages/17/8f/168ba80e532dd6a93f96f8f706f1ad41d7990b6e1aeedc1cc0d211a33497/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453", size = 11589, upload-time = "2026-10-02T23:05:33.251Z" }, + { url = "https://files.pythonhosted.org/packages/32/b3/aa2c95a574d3af39403a469b295886eb9b6d448da568cbebb5a2cbfdc2e5/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b", size = 11801, upload-time = "2026-10-02T23:05:34.315Z" }, + { url = "https://files.pythonhosted.org/packages/60/d0/34b810107d83840e768bf485de795893ebbae35b26ab061b487adfa0a692/markupsafe-3.0.4-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6", size = 11447, upload-time = "2026-10-02T23:05:35.302Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ab/2f8488f0f817a39fca068d2b17daf446bf5cdb3eae28c3720af534d873b4/markupsafe-3.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634", size = 11722, upload-time = "2026-10-02T23:05:36.363Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/e2d117b048d47282ade906fbfd92814cbee5647afc13fda88a3406039372/markupsafe-3.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f", size = 12061, upload-time = "2026-10-02T23:05:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a8/73a81135e85ba66217f5af7facb03bbb386807e1a729ab64532e4c802652/markupsafe-3.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9", size = 24434, upload-time = "2026-10-02T23:05:38.407Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/fa9216dd01efee2dfdacafe7df32b4d0170fbac694b0c258a193d6e53999/markupsafe-3.0.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f", size = 27183, upload-time = "2026-10-02T23:05:39.581Z" }, + { url = "https://files.pythonhosted.org/packages/fa/4e/a469509e538d37af51103b17b073126973f2b1cbf197ff32c7ddf025cfe5/markupsafe-3.0.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c", size = 24813, upload-time = "2026-10-02T23:05:40.671Z" }, + { url = "https://files.pythonhosted.org/packages/8f/db/d7282caf7ab03af44d5d6fdbaa019b35c7d7f1c90588b839c07cba640d6a/markupsafe-3.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300", size = 23049, upload-time = "2026-10-02T23:05:41.864Z" }, + { url = "https://files.pythonhosted.org/packages/30/f3/b6a425206e6964efda6acee544d0eb01d1501784d0b8e2dcc74986f33b17/markupsafe-3.0.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0", size = 22088, upload-time = "2026-10-02T23:05:43.014Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8a/84d3582fc1f0d5bd466cdf2eebf175e172158a6e70701aacec1de1b35430/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977", size = 23846, upload-time = "2026-10-02T23:05:44.098Z" }, + { url = "https://files.pythonhosted.org/packages/1c/65/db101cce51b7ba4864ac491a9859d297dd1adf0e55b103fee9db9c47c527/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7", size = 22674, upload-time = "2026-10-02T23:05:45.23Z" }, + { url = "https://files.pythonhosted.org/packages/e0/49/ddee9813d71db0c7a5c9d97c832125e6758a0c844777f1cf076569bb0e22/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17", size = 24104, upload-time = "2026-10-02T23:05:46.398Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0e/7d8518d726726870a2399d69fd30d0fa36c5e57a2132c336b58d7c491073/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c", size = 21637, upload-time = "2026-10-02T23:05:47.48Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b0/b505e8a361ba557dbf3b3aa7331ea39b00d2022a26e925ff8463b9714bb3/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4", size = 23081, upload-time = "2026-10-02T23:05:48.611Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ea/9cc3cea873f980c75cbdb6f4277ce30ee955de38be0b3d02f14c108e0698/markupsafe-3.0.4-cp314-cp314-win32.whl", hash = "sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c", size = 14272, upload-time = "2026-10-02T23:05:49.707Z" }, + { url = "https://files.pythonhosted.org/packages/80/f0/5792ff768a410f93ee3f84fc19345295ffc352d2c936b424cb37e514714c/markupsafe-3.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe", size = 14501, upload-time = "2026-10-02T23:05:50.788Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cf/3d074a8edffcc6899355232ff2543ae8d929733239596423b7db79698bc9/markupsafe-3.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a", size = 14364, upload-time = "2026-10-02T23:05:51.857Z" }, + { url = "https://files.pythonhosted.org/packages/d9/31/87ce42159aae2163cf3bbbd0c44bc87780510eecab1ea3859099aed95dcb/markupsafe-3.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2", size = 11759, upload-time = "2026-10-02T23:05:52.951Z" }, + { url = "https://files.pythonhosted.org/packages/5f/53/b047207eeb7752e960aca3eb1df5fb7eefa7dd4c62ac49bb156456c8a702/markupsafe-3.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977", size = 12096, upload-time = "2026-10-02T23:05:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/4326c88a13c7b755657d44b4bb986f8c3d9843ecba7e22d98661d87f9a57/markupsafe-3.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289", size = 25699, upload-time = "2026-10-02T23:05:55.15Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bb/990581b7474bfcf2cf34bed6ba5ea23bd87adb9d671213d68e88620e7a6b/markupsafe-3.0.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe", size = 27804, upload-time = "2026-10-02T23:05:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/6b/89/89491878c28e8291f5aa2fffe2c2d57230d10ae366d55dd810b840513d78/markupsafe-3.0.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a", size = 25912, upload-time = "2026-10-02T23:05:57.416Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/680998b54efdea06fc114565cd739b6d059f826a0279219b218dfa750d29/markupsafe-3.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733", size = 23623, upload-time = "2026-10-02T23:05:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/ae/75/2709f5ac5de9467b40b10e2bb8f89cc63dfb74582e09aa734b1124a217de/markupsafe-3.0.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34", size = 23301, upload-time = "2026-10-02T23:05:59.94Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c8/39eadc6c5b14c9c7679bfb98f4d4c6a97863b5beb91839aca4d2d6e16e55/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978", size = 24950, upload-time = "2026-10-02T23:06:01.289Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5e/01037f8a43e8ccb0bffb4fbdc5212db05bf080fdd7286cd392332d58128a/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc", size = 23330, upload-time = "2026-10-02T23:06:02.441Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f4/23e83ce0596bb0cbe670502d31df8f757bbd01a392aa486fa3b40d1ed399/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc", size = 25239, upload-time = "2026-10-02T23:06:03.579Z" }, + { url = "https://files.pythonhosted.org/packages/88/5b/3708897368073cc683d524750474f41a77d2986152c380dcc55b20fdf340/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932", size = 22767, upload-time = "2026-10-02T23:06:04.699Z" }, + { url = "https://files.pythonhosted.org/packages/c6/61/ebda1307864b409e6b3115757a3d4a09cca46cfb6cc65191b5de226b424b/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6", size = 23668, upload-time = "2026-10-02T23:06:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/09/15/98075cceac3b5ba0dbb8e4762a847be967d2befc349a2cf2d0ac77f62c9d/markupsafe-3.0.4-cp314-cp314t-win32.whl", hash = "sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691", size = 14301, upload-time = "2026-10-02T23:06:07.109Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a3/768b560fcc4156685cb563d922b217810cfa7bc135773367f62f1f9d2078/markupsafe-3.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464", size = 14526, upload-time = "2026-10-02T23:06:08.276Z" }, + { url = "https://files.pythonhosted.org/packages/93/63/da554b4c97a6b0ea3229ca7fe8cbfb620be81613d517f482e85958550537/markupsafe-3.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c", size = 14421, upload-time = "2026-10-02T23:06:09.402Z" }, + { url = "https://files.pythonhosted.org/packages/a9/30/54d11c8ca027114898cab97421fb39e4ffd9ddf47cdbc44df2ec76722da9/markupsafe-3.0.4-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65", size = 12791, upload-time = "2026-10-02T23:06:10.485Z" }, + { url = "https://files.pythonhosted.org/packages/10/6d/97c913e253a14bd3cd0e15a5c56d13203b823fa7ee32498342896a072dc4/markupsafe-3.0.4-cp315-cp315-android_24_x86_64.whl", hash = "sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163", size = 12527, upload-time = "2026-10-02T23:06:11.834Z" }, + { url = "https://files.pythonhosted.org/packages/26/f9/b86d032042a4d597d9e1997f0e5f63a3eedaf11258e0a05760b0a0a826ea/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92", size = 11602, upload-time = "2026-10-02T23:06:13.122Z" }, + { url = "https://files.pythonhosted.org/packages/f2/dc/73c14c1eedf0ac5fa3292ba43435e6c49d2c2050f33cebde541f8f4807f1/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a", size = 11817, upload-time = "2026-10-02T23:06:14.227Z" }, + { url = "https://files.pythonhosted.org/packages/8f/69/2c2fcaa5fcee22d72c7819c0d536fd181c74a688e6143845419579cd2863/markupsafe-3.0.4-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429", size = 11446, upload-time = "2026-10-02T23:06:15.574Z" }, + { url = "https://files.pythonhosted.org/packages/88/54/9e5ec76c62e6e2834d5a93623018c943e8b3bb41d663e3fd4c03303b9b85/markupsafe-3.0.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8", size = 11719, upload-time = "2026-10-02T23:06:16.701Z" }, + { url = "https://files.pythonhosted.org/packages/96/24/3ec292b44064c16229e064d770b2625bd8ea941aa61f44905a9fa44942c0/markupsafe-3.0.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97", size = 12075, upload-time = "2026-10-02T23:06:17.855Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/b64fdb1f304848518742136983c24e96d967bfb59a0ea160e92736901ab0/markupsafe-3.0.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b", size = 24656, upload-time = "2026-10-02T23:06:18.963Z" }, + { url = "https://files.pythonhosted.org/packages/9c/18/23997d4c65b355da6390d61cd56e0ab3befd6ba8dda25cb40c602bd0fa6b/markupsafe-3.0.4-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9", size = 27737, upload-time = "2026-10-02T23:06:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/35998dead3c6af88c38265a56e58100211f036234ab88eb2283fd4cbce44/markupsafe-3.0.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653", size = 25003, upload-time = "2026-10-02T23:06:21.284Z" }, + { url = "https://files.pythonhosted.org/packages/82/96/ef49135ce260db4ca4a12b119ed468449cd248db6b1468e2112b546d7a2e/markupsafe-3.0.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369", size = 23111, upload-time = "2026-10-02T23:06:22.524Z" }, + { url = "https://files.pythonhosted.org/packages/50/7d/83126e338bd88c17a220668235368ad719fd4638e426739858cbb8508f77/markupsafe-3.0.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19", size = 22403, upload-time = "2026-10-02T23:06:23.785Z" }, + { url = "https://files.pythonhosted.org/packages/83/dd/daf7e420de23c8206c365204e7b85e1251d8e19d34196a56336f316e5ed2/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e", size = 24083, upload-time = "2026-10-02T23:06:25.037Z" }, + { url = "https://files.pythonhosted.org/packages/19/3c/11eecdc06bc44ad5570350085b572ebf049e8f9a38d1ece6d76640b739cd/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811", size = 22931, upload-time = "2026-10-02T23:06:26.328Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9e/ac0fd77f2a726e56ecc3ca0235d095feace1358d1b822406c2a2ef26a4dc/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea", size = 24299, upload-time = "2026-10-02T23:06:27.742Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/c6bd842ad58ff5b3bc76eeed7e9a42a6f11adc5d090ec697b72c9672731e/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916", size = 21948, upload-time = "2026-10-02T23:06:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/82f586711fed61e86faa1ee1bc317d68cd45a10c8bdbe3f7d1fdf9026ad8/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741", size = 23150, upload-time = "2026-10-02T23:06:30.583Z" }, + { url = "https://files.pythonhosted.org/packages/19/2d/2dfdce99318abbfa26925195fbc17db188c46a1ec6457be121b6f9cfeb42/markupsafe-3.0.4-cp315-cp315-win32.whl", hash = "sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b", size = 14269, upload-time = "2026-10-02T23:06:31.949Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ec/6000fd82e8791e58fcd0456ec20f098957e2b03d5ed02eb73241a577c0ba/markupsafe-3.0.4-cp315-cp315-win_amd64.whl", hash = "sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214", size = 14496, upload-time = "2026-10-02T23:06:33.258Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/e73bd5016421d5d6e2fb6de7dd609f9de020942ac8c626526bd8c6eeaf82/markupsafe-3.0.4-cp315-cp315-win_arm64.whl", hash = "sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67", size = 14361, upload-time = "2026-10-02T23:06:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/90/df/cb8c3dc98d313a951df2f8968f44e4cb5643df6d3cab749a530ce2f7d972/markupsafe-3.0.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad", size = 11760, upload-time = "2026-10-02T23:06:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/d6/bb/4af9b3ca0753d654ac75f9531d5bd741bb77ca6e696f36807c475ffc099a/markupsafe-3.0.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99", size = 12108, upload-time = "2026-10-02T23:06:37.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d4/b56429313aee5fd59b079c3df5615299959e25e7113eb6d8caadbdd7d38a/markupsafe-3.0.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002", size = 25762, upload-time = "2026-10-02T23:06:38.419Z" }, + { url = "https://files.pythonhosted.org/packages/65/f5/34c181e891aa4f7d59c918584672e0c5eb7fffe76c1387d1246008bf4081/markupsafe-3.0.4-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e", size = 28400, upload-time = "2026-10-02T23:06:39.819Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b5/ad14694fd0ac9a5ce30bc6498f2999378f418583dd1679cca5a1b512957e/markupsafe-3.0.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c", size = 25946, upload-time = "2026-10-02T23:06:41.381Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a8/26b606445387d0ceb1eb1f21840094b84e4e3c3c3983d80d10b89823b490/markupsafe-3.0.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8", size = 23634, upload-time = "2026-10-02T23:06:42.748Z" }, + { url = "https://files.pythonhosted.org/packages/39/a2/b8814de672f1f0094d498bf646f2fec9d6356b503d28ef500b71c5095377/markupsafe-3.0.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe", size = 23488, upload-time = "2026-10-02T23:06:44.176Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/287223376fb73335a3cc5d6eb22c6ab01358cf33945a9c39c06b9dac3f4b/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2", size = 25037, upload-time = "2026-10-02T23:06:45.646Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/4df8355e313426d19e62ba33e0253c009ca12a0894ee77d67fa67255361c/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38", size = 23543, upload-time = "2026-10-02T23:06:47.264Z" }, + { url = "https://files.pythonhosted.org/packages/71/e5/8377731e8495668dcc768f645e717df18318c841edaf023a99395f6da9b4/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494", size = 25282, upload-time = "2026-10-02T23:06:48.795Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5f/373456e37ceb1478d657d6fe769cbe0a39f0a8dfc1548eeb19c471eefdd9/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d", size = 22993, upload-time = "2026-10-02T23:06:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/2cbd5628435afb6f541bbaced4bce0c2edac4b09a142e6e928b8b0da9858/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894", size = 23712, upload-time = "2026-10-02T23:06:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/157e10966b033b363aeda5263e82596ee232a0b1d082fdbf90aa417ff083/markupsafe-3.0.4-cp315-cp315t-win32.whl", hash = "sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78", size = 14300, upload-time = "2026-10-02T23:06:53.241Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/55884815414c9706a23deca150b72c25a62109e65b0b6ce232077802c719/markupsafe-3.0.4-cp315-cp315t-win_amd64.whl", hash = "sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c", size = 14522, upload-time = "2026-10-02T23:06:54.729Z" }, + { url = "https://files.pythonhosted.org/packages/92/f9/ecbde7149e95b8a0f18e16d5d747f7dc06049d5da2e4f77f6f5e4a1f46a8/markupsafe-3.0.4-cp315-cp315t-win_arm64.whl", hash = "sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba", size = 14417, upload-time = "2026-10-02T23:06:56.246Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "multidict" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/79/84ddb5ba16c4eb2c69c71db76ae3c579fe546e511f7170c7e27eedbab7c1/multidict-7.1.0.tar.gz", hash = "sha256:61a4e5d81b8d4e4ad61964b230129e7a2b914793d96289029078fc9009f074ec", size = 362341, upload-time = "2026-10-09T20:31:38.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/27/77366104f779d883f1a13ef3e8e274cbc2adf75847afecdee0970ace37ed/multidict-7.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:24ad4921135a1410d95b1f1504f4901e1c64cea680014ce2c3c7a825f4f259fc", size = 166031, upload-time = "2026-10-09T13:58:50.692Z" }, + { url = "https://files.pythonhosted.org/packages/02/c0/dd8df25b15560bd6dcb1d844150ed72af8fc2f2b52e29b4362b22c91383d/multidict-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b8429361241da973e594d15344a0989f44fd288ea58d33a6221fb7cc0daf27e", size = 95884, upload-time = "2026-10-09T13:58:52.034Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/e759ae1d3a44db8af1c6f37e1a0d290122b455ee4ebce4b989705b5a8787/multidict-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c6455f2c11daeee40665c67494cedb426f67dba7375710524071c0c56d739a6", size = 99026, upload-time = "2026-10-09T13:58:53.365Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ee/898d2a18bd1ef8b2adc20c7d8ab8f027f2b9e5da9cb40675133778e26215/multidict-7.1.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6120aab922bb3e15800b6655558cf8e0a5cc79518e954d457f064e5b3d5e9bf6", size = 436692, upload-time = "2026-10-09T13:58:54.57Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/2a6994dff5c01685ab08db1a88bad2579fa1db3e74e568cba632118242f8/multidict-7.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14b1ce8579a43dfc0e592d93fb1d63dea693e4977980ac4166f26d494cc7a358", size = 455754, upload-time = "2026-10-09T13:58:56.159Z" }, + { url = "https://files.pythonhosted.org/packages/48/27/353916a8c627067b54723792f172813025f87f9f70f9771c27214f748b01/multidict-7.1.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:71acdc6eded0f4b86b5e16c96314887cf2572a8eb5d8038b78583d0c0eb3aa1c", size = 427471, upload-time = "2026-10-09T13:58:57.569Z" }, + { url = "https://files.pythonhosted.org/packages/62/f4/08993b1ee8e208dbf6cdcb5b536f4dd261211cde2c864cdcc2b527eb974b/multidict-7.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b7cd1cb0b363cd43ebf499beca26d201dd8b89eee49fae60205c82ba13ee03a", size = 477087, upload-time = "2026-10-09T13:58:58.879Z" }, + { url = "https://files.pythonhosted.org/packages/ba/02/31f6890cc54c83de25df712fab9300ef7b13edef463aa7e0f68e5afe40dc/multidict-7.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dd8a6b3e8f9edb07fe671b02d8c3241c8b641fecce7eb1e36432db3e55e243da", size = 488933, upload-time = "2026-10-09T13:59:00.632Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9c/598d765e98aa02ff7204b6f11cf790e5840f5e8252d7b1d955ed0043bb94/multidict-7.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a8c826caeb7c08264e0a556df1267531c6ed90cc70506e7e5f4119e2d09f3d7", size = 464425, upload-time = "2026-10-09T13:59:01.955Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/b6b3508ba72bb82944f985cdd9c4d398c442c07270e5179ea03262b9bf03/multidict-7.1.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c44ced5e5168cdf677f0ae39900863bf2bda7d14a5e13502014005cfe040b8b4", size = 420397, upload-time = "2026-10-09T13:59:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/8c/81/596582dfcf8d3c2d41ec03f24849be9e53c8a44aebd40cf45a31720112f5/multidict-7.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b57d4d7021bfd159db9f8f6f862a85a7a6027934643c512f028d6e5c60c4cbd2", size = 445983, upload-time = "2026-10-09T13:59:04.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/aa/600a68f29c2b027c2000e4ec6f51824790ff9a13a3828487716e31ac3277/multidict-7.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7fac4250b37d994e3fe42b46ba3c8bfa1614d1d7d8cf1cf23f303099082a9565", size = 421056, upload-time = "2026-10-09T13:59:06.469Z" }, + { url = "https://files.pythonhosted.org/packages/c1/82/006ef3fb5f2b798e9e538125667ba1b90ed8967bc862d7b9103297b2bcb7/multidict-7.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:966ae0588ac9959a040220063733b33f321d04eaf4e60349b42cd855d232202f", size = 436830, upload-time = "2026-10-09T13:59:07.882Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6e/ffa1117db64d4be681f908cb7f38877c70cd06bbbdf29c8685839dcff7d2/multidict-7.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:50acd7ee7096949b04482cd7720cb6b85eb9cd9dd5d7ffb6704bfda250261a22", size = 465434, upload-time = "2026-10-09T13:59:09.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/8b/f3ab95a4fb591467c0d2a3c0876f38c17aa0b5c2cd60b4c53bc0c9867c7d/multidict-7.1.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ed6b7f402f3dabd1d72c798b96cf947005ddd796a5bea7b041bccbd517859a42", size = 417108, upload-time = "2026-10-09T13:59:10.595Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2c/3e929884b0f3fd4ae96572d62952e18a76048115832835fb11075295590a/multidict-7.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fadcc96cd6155f35e6d85845fa4fcd37b35885dc8fda77b9f851cdfa538194c1", size = 471643, upload-time = "2026-10-09T13:59:12.082Z" }, + { url = "https://files.pythonhosted.org/packages/32/7b/0c3451ada3a85c1232b04ed0209d5421b07ecad1f69d1b55939b3be0cef4/multidict-7.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c6b67f08014bfc4aedc22cf6a21010c2530cd5fbeb655730406827fe196296be", size = 459115, upload-time = "2026-10-09T13:59:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d9/aa3b846e69c3730bd999f4f95943301ac26d999d9e285e75b9759c340a45/multidict-7.1.0-cp310-cp310-win32.whl", hash = "sha256:0604ff025497a050a2b2dcc4ae0e5cb6477c525e57b89825152c707e88d74d28", size = 81811, upload-time = "2026-10-09T13:59:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2d/f4907e688463dedc1e644777851c0aaf2b814f37278436f9d3f4d3225a0e/multidict-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:36b14886aa3e0b8786ecdaa196374422c7b1c1dcc8764d02b2409f74d47914bc", size = 92126, upload-time = "2026-10-09T13:59:16.222Z" }, + { url = "https://files.pythonhosted.org/packages/16/b8/5f438830119ab1979245c07bf61038813d2f7509f38d1031d6b8e2114bcc/multidict-7.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a2e575129c048bc286d696ed8e49ca148591768b2d77debcc6569f6fb64d0668", size = 92561, upload-time = "2026-10-09T13:59:17.47Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/c68c86c078a3b3cfa254bce218fbeaf1f6617d4ace811558154ae63d2c20/multidict-7.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41ff3202cc23c800507777df5a4805b402f262b31008c60fdc652aeb6db2f278", size = 165442, upload-time = "2026-10-09T13:59:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/988c71ec3a6fe143b2971ac8d57cd5aca7cc6da970bccd1e4c2254997a09/multidict-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e50f7775b66c7802f4cb697e986c5acf30ec07301efee95b396c08114e890d67", size = 95611, upload-time = "2026-10-09T13:59:20.591Z" }, + { url = "https://files.pythonhosted.org/packages/30/5e/7d18eb5a75cf4bacb6b3ee689929dcb27d7ed8ca2d245e80e9b8056fcbf5/multidict-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85cb3ced4fa84949cee12bfe78208b6ece7baf3cbd242b26dcaf773efff8d206", size = 98743, upload-time = "2026-10-09T13:59:21.841Z" }, + { url = "https://files.pythonhosted.org/packages/72/38/2aeff3de6ddf235094aad6f833e3df14d1b3175e328748508443cb2b5458/multidict-7.1.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2128f3358335e0c83688ecb40c19d9d6606cd60784dfbf2e24e980ac2ba87b0d", size = 452832, upload-time = "2026-10-09T13:59:23.131Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1f/94d8b7fc7d47bb43be1fc041bccc2784383d352258d4d0dd90ef55d1a2e5/multidict-7.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2e718fa9d1d900decbc240a533d5d0baf0947ef464c78a8cd4fa32b4e8f590c", size = 475378, upload-time = "2026-10-09T13:59:24.661Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/78f431ef2735fae7a773261ea26cff11e37cbbb4520bf3caed56ece006f1/multidict-7.1.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ecc68f5e47bc6f6f889bbed5bc657b22bb2237ad9ccab8229cb5a0d64f4cb536", size = 442847, upload-time = "2026-10-09T13:59:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/45/d3/1c5af82fe42f677f7c4dcaf487449511d754b84fc29f11a6d190cfb01836/multidict-7.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5f21fda91bd6c34455bd5c312e42aa1334da46cdafb4c533ecd01e0f7f19250b", size = 493652, upload-time = "2026-10-09T13:59:28.162Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bc/414b4a83a12811329c046a7c1954d6f08a5eda63f8466939a6cb612da22b/multidict-7.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a90453a79423cd7145cc08fc92322dcd7aca4862258f533e03f473226d4b835", size = 506991, upload-time = "2026-10-09T13:59:29.701Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ac/a91709d7153489a3b53661b62af371112b96fb083d10443e22d54665a1e1/multidict-7.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c54ae1b89e582aa25f213cd8b5eac0bda1724e79299f486baeb3f562bbf82ca5", size = 480392, upload-time = "2026-10-09T13:59:31.179Z" }, + { url = "https://files.pythonhosted.org/packages/72/63/a4fc53b6da419876f363fc6860f9fbaa48eb8066f458e907eb2989e9510b/multidict-7.1.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ff8dd079e7b5f3438332499233a2a5acfca0741fd0eb3d4ddba0c2d9bc04d19", size = 431248, upload-time = "2026-10-09T13:59:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/3d/78/08af4a624b3b9d2f48af730c173394135d14afbf4b3de5ae6c80e0900fd1/multidict-7.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e4ef15d0a29fc2da67fe8ba2301ecabd6f8733696cc2bf0a0cf96a144a20328c", size = 463657, upload-time = "2026-10-09T13:59:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/58/ef/06e5c75b88506972c880d228fe96702cf0fa058ea31f157f5fcd5efcf006/multidict-7.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5fa296f14068538fced53c6eec86520a2ef3d3d27a0fb134640d03e067986d5f", size = 436120, upload-time = "2026-10-09T13:59:36.135Z" }, + { url = "https://files.pythonhosted.org/packages/23/8d/a917004a1e8ceb325c4cf0583e418a40b7ff9519cbfe759bc6982cd3b4c1/multidict-7.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6ab323f0c5490abaf35a78563e1043c7a772eb86d93f359ecc0fd286d1cd3807", size = 453859, upload-time = "2026-10-09T13:59:37.878Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0b/d1cc417355625152b611790aa1f0bc780983ed8c0752f44087fc7dbe66c9/multidict-7.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:88ec4d16e9f58071c9896ea01c4da97cce9d01418fe844ff06eebb00e0a1386a", size = 481768, upload-time = "2026-10-09T13:59:39.39Z" }, + { url = "https://files.pythonhosted.org/packages/94/0b/8e79adf65cf5430497d1020504b7d86ebe90e3289a95bb6b252a630d761f/multidict-7.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9161eb81b8062da824426d3700d4b0d287f0cb0b05923713adfe3bd25e7937ac", size = 426973, upload-time = "2026-10-09T13:59:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/e84d6e05943600b16cd2745587e65a407f89b3a4a1f77081d456c5a07011/multidict-7.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c564d0758748f38aec56a6b98c6801a427b3a63f39b7cac538b2b2d18ca32740", size = 489681, upload-time = "2026-10-09T13:59:42.663Z" }, + { url = "https://files.pythonhosted.org/packages/eb/98/01951236a01b3e15c037a4a796f329b5099374e46365d0bc65bb4a7b325d/multidict-7.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c5e4a362a95b85301d262ef6bed06cc8e4a144ac7e2be874cb4c3c46ae89d754", size = 475655, upload-time = "2026-10-09T13:59:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/d3/72/52be318cdb46732da8eb118c2e4212fe570724eaef817901f8b702af2016/multidict-7.1.0-cp311-cp311-win32.whl", hash = "sha256:5d19bb1ec12e385c09215d5d53a243c060c7e8a0aacdba16d933e22902ee380d", size = 81719, upload-time = "2026-10-09T13:59:46.158Z" }, + { url = "https://files.pythonhosted.org/packages/37/fd/1ed7b7d206ef7a6918ac3de515543ca4b49bc50131325ca1814c005daf3f/multidict-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:396ba9917fe489ec3a5942ae3e29e91324c8b9956f371f7e124c971c71379e7a", size = 93586, upload-time = "2026-10-09T13:59:47.618Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1e/837877d218bfd2b656ca899ec9dd83e6eb82d8e1ef16e896d5abf3cb60cd/multidict-7.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5ed78742502b8d90ff2816688d407a097c8b5cc6af4343fc5ad7a98df53a7cd", size = 92446, upload-time = "2026-10-09T13:59:48.996Z" }, + { url = "https://files.pythonhosted.org/packages/03/e1/215e7df354e2907f3af9d910c8136653cfec94796012a776359bc802a326/multidict-7.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ccfb950359a80de0fcd2030ad60ac1b1a861462de3e2ef746697c9256659af21", size = 165426, upload-time = "2026-10-09T13:59:50.564Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ec/461ba588b308ada2cd16907d6ea425ca4d417596b88c12814ad9a0bb7325/multidict-7.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:939d8cd2d8c35e3956f6bc858390b6ccb611e6152b4920d64ab5e98f3fcf39e4", size = 95943, upload-time = "2026-10-09T13:59:52.381Z" }, + { url = "https://files.pythonhosted.org/packages/c9/84/31444ef07ec13a33c42c2772d986129e137e69c4beb89ca64f2138988145/multidict-7.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f8e95c95039eab6a2dad8c83c38ab87fc5431d28849e0c8a7e2a4e70ba38710d", size = 98294, upload-time = "2026-10-09T13:59:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/dc/10/aca13806d73d88b5b01e35e828e23a346cb1abad710a49dff0507702efc9/multidict-7.1.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:05d12b4bac53abe0c65f3163af2b45894e2e1c0cc55493ac784d52a350047d88", size = 453301, upload-time = "2026-10-09T13:59:55.397Z" }, + { url = "https://files.pythonhosted.org/packages/96/8c/382d771bfb3a9282d98332d0e1f27fd1f8b4ae0e7175bd7e008ebf934900/multidict-7.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e79ed92b1dece6bb57e9b46effd74d7a5d3d00187c85466d880ed184239a698", size = 478570, upload-time = "2026-10-09T13:59:56.899Z" }, + { url = "https://files.pythonhosted.org/packages/52/9c/e81b0c92449da3a1950a575c520d957d7be618777d170717a71e00558d7f/multidict-7.1.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fab380fcff8b3555eb2bd04304fa4330909a771a9a9b0dc07666cfc23148a711", size = 443699, upload-time = "2026-10-09T13:59:58.465Z" }, + { url = "https://files.pythonhosted.org/packages/63/72/f8f5fee6960d1b580a7b046c7c5abccf67aa26fa5647927a91c9c835c2f8/multidict-7.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b5c41e44da74383c924cc5d75ef0a268f301d69305b3c42bd17af685d55e412", size = 497332, upload-time = "2026-10-09T14:08:01.88Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/a25d7db3ca451b588e743e067ee80f2b475edf6467a56908454faf6a714c/multidict-7.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dbaa7f7c2f0ca8578895fc61fb8c8e50ebb405dad8982f92f4343285c7a3fda", size = 512457, upload-time = "2026-10-09T14:08:25.465Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f3/374c0ab122bb98b1a62a8742b1e3f59563e4d609942005a4041861a0df67/multidict-7.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed6b7705d49dd07e5e0dd5f5c873fc44047e92d714299b13245b5fecac49d01", size = 483000, upload-time = "2026-10-09T14:08:27.132Z" }, + { url = "https://files.pythonhosted.org/packages/46/1f/01c8522859771dc3cee840d5852233fe84c91aa982a1cd8ad594306d25cc/multidict-7.1.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53daa47dd176db64bb35170e3d5d0ae2388c060121201883696278f055a0e70c", size = 430746, upload-time = "2026-10-09T14:08:28.715Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f3/af90affc8cca6b4ee59b2ec354a20839005e829d14d155451009810ef1ce/multidict-7.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:542429c796430de924d03b68a6173bb6d79d5c4967d4e9a18de3e501cad55593", size = 466859, upload-time = "2026-10-09T20:27:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/f8/97/1b6762f37f6331449e164af9d5500f0abb0f23670e81ba543441e0a6be1d/multidict-7.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c44ca6d3cdf4cfcbcd4f928fdcbe87af5fd7319f6ad4169617b7fd6b4527c33c", size = 441952, upload-time = "2026-10-09T20:27:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d2/4908177fbf22438799c04ba11a2853a99c69d028fccefe61f19e68caba0c/multidict-7.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eb0228c809b2e7eb47921876050af0bc4214b351bad8d8112f70b6ed4288763c", size = 452810, upload-time = "2026-10-09T20:27:28.347Z" }, + { url = "https://files.pythonhosted.org/packages/8d/05/5031f44f680ec54fc182c4d71c9ab7c07216983946aa64ce6fdd56a52692/multidict-7.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6ad60de1f4c702448fc8f1449f05e810f6b7957c08a5b3950c8a792dfb13b50a", size = 483934, upload-time = "2026-10-09T20:27:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/da/3b/9b21d107dbe96fa7e6966ff9e5e10b9ac0d2d1c2cf1633098e4c700f865e/multidict-7.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f79def86aee67b5ba01b2565f1610f262bf88ae53c379f93e5fa29c50fe793be", size = 427042, upload-time = "2026-10-09T20:27:32.433Z" }, + { url = "https://files.pythonhosted.org/packages/53/ce/5b01b1041580072866b30e39c6380bff269e72fb5ca41a7f2fb828ede943/multidict-7.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:248dabb89b5aa90b2f7e43e045f048f7e5392ec77b6446d80853ba7117d7bbdf", size = 492543, upload-time = "2026-10-09T20:27:34.459Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6f/6508a23fcc7b1122e4f18409d7900ffeb3cd020cd080fe98aba3eabf9486/multidict-7.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0747a83e7ae617793181a4763ee8b84863cec5c0bbbde70c4394e4c0276c36de", size = 475796, upload-time = "2026-10-09T20:27:36.289Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b6/ebb6433f4aa55ac1fea4bf11e860e99611640d07cc92d21cb3f35608de22/multidict-7.1.0-cp312-cp312-win32.whl", hash = "sha256:1df055e51fe7491120cc84f3362bd43db186be78d0e4c476acad45e435af9ffb", size = 81250, upload-time = "2026-10-09T20:27:38.229Z" }, + { url = "https://files.pythonhosted.org/packages/61/0a/11240e5e7d2e986e288f4a6090f569ad00225b7c2c513d4096b36643f9ab/multidict-7.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:10202ba98cfb3f7eb60da7ca87a2c458a69b7d0d6e4d4388cd6773ebbce89085", size = 93817, upload-time = "2026-10-09T20:27:39.739Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9b/a04ffd76db7cca2a60d347e839315e1ada63017c6c343fa90a23a5a55433/multidict-7.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0aa1ba3ff7cdda05a1242490612976b2ae1c90fc6200903ef8f53815dcb35c5d", size = 92312, upload-time = "2026-10-09T20:27:41.285Z" }, + { url = "https://files.pythonhosted.org/packages/60/34/22ee5f60d704e8edfe6e0a37ca1e2d1efe276560dc8f6f8caee35ea7a4f5/multidict-7.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:51d7f33be9a4a1a2801430846d72841deea0894eae8381a07e7d90e0f71b3c4b", size = 101454, upload-time = "2026-10-09T20:27:43.102Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6d/7652943397171fd624de163d25fc6d2807852931c40c24776cfee3f857cc/multidict-7.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2a964dfeb2aba3663f0536c809aa1ff385f065e89fae57e883fb7edfb4067c2f", size = 95823, upload-time = "2026-10-09T20:27:44.912Z" }, + { url = "https://files.pythonhosted.org/packages/a2/57/717d1aa4e901f58a7e93b945ed741c860d950f456b75be47123fef507ac2/multidict-7.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d02cd23b5af182a49d635ee72be38053767711987a9fd82625b16b93828a0d8c", size = 97841, upload-time = "2026-10-09T20:27:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/a8/74/d2d22d306225f3c53ea5a682abb8a43bc30057d6c78b5c94a74035450bfd/multidict-7.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0179698c3c913eb64f32397083747fad20ed0f0a2b7469a08cd1a8a95d14d90e", size = 164661, upload-time = "2026-10-09T20:27:48.495Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/1e63fea41ba86768e18dbc1743e6780f75ad32d7a45e418740c6fb64589e/multidict-7.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ccf98ee859fe29f874ddd8e637f14ba59108a333492b521acb885a9095244a9c", size = 95489, upload-time = "2026-10-09T20:27:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/477c351b21b34fe948ffffa8b64cd0c246efd998fed3de922b217e632e59/multidict-7.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5fa1484f74d011addf2e5f5a0378ec41521989839a05d6051d8067d8ce732423", size = 97931, upload-time = "2026-10-09T20:27:51.598Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dd/288508d7deb9489dd7c8e0b172d62dc1da8f3cb24877293ead42e6cc2047/multidict-7.1.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1fed3d721f75c25a9fcdd0e362af53f4b20acbcdc63081112f85419ba0ce3444", size = 453816, upload-time = "2026-10-09T20:27:53.217Z" }, + { url = "https://files.pythonhosted.org/packages/60/ed/172447dd09f06111f69d2cf22a018020b34a93a39ddf72cddfdb48323449/multidict-7.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df03e392cae1e05462918abbae06d6100f1e53f67db971ff0ac6c07d9edf7321", size = 477779, upload-time = "2026-10-09T20:27:54.972Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7d/e5b7755e84611ee846f0dc830cb1363ce10764b29247a01b857722fda653/multidict-7.1.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:379f477b98a1e9a77ddc3ccaa8c709d3fb4a288ff54b96e171e637b55b4adbae", size = 441299, upload-time = "2026-10-09T20:27:56.901Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d6/e9c93da1644491610f09258349283421a279fdb3b383929b4d963698691c/multidict-7.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:40f586bc8a084a3671ddcae9e5fbd3228a596bfb63d9f0380f153f9a65b69f08", size = 496193, upload-time = "2026-10-09T20:27:58.788Z" }, + { url = "https://files.pythonhosted.org/packages/22/49/7fe19efed1b1c73e2f6ae65eba4e6528eeb26e10c970dc0bb0fc009a2aae/multidict-7.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e7f70d912a589e30290ed926f90ddbc3160998359cbad7c9ede1bcee481748c", size = 511469, upload-time = "2026-10-09T20:28:00.689Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2e/7b708a72001323dc6b2930834d63e355870ea5b1ee8d1450e0fe1a24752b/multidict-7.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5129cc1f5fec6888e2db0be936dab67242e32c738811c8769aeea93aab4257a8", size = 481377, upload-time = "2026-10-09T20:28:03.011Z" }, + { url = "https://files.pythonhosted.org/packages/8b/60/3d23e7d2ebc7e7e6b1b205de0d5c2ce0652a9b2edef65eea3528758b5699/multidict-7.1.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1401caec21fd7f002e79ab6806bbfd1f54bb3de6d5e12bd91c6685dce16ad2be", size = 429992, upload-time = "2026-10-09T20:28:04.846Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/8e3bef78ea1929a3f2c395aa292799d4effd6ce1b5cfce42c1986a33e2b5/multidict-7.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c81062e947f4b5a624135a843f6ac4b3c7fe6508300c9fb27347f022ba0c513d", size = 465635, upload-time = "2026-10-09T20:28:06.527Z" }, + { url = "https://files.pythonhosted.org/packages/44/6a/55fe5dd90c0e9ce60f5c4fa8ace27eeb1190d9cbb151929b04412f0c673a/multidict-7.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c53be0dd676484a660acc56e4f1cd0dd74bc1255d12fa285e86a3fa9d5f22bf9", size = 441693, upload-time = "2026-10-09T20:28:08.443Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/628913f71537dce7dbd6c4ee1ae5019976264427c55354ecc593b4b921b9/multidict-7.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:41e0c3350d08994ee8640c39884e16514e282f70ba40f5b2299582509a327774", size = 453808, upload-time = "2026-10-09T20:28:10.871Z" }, + { url = "https://files.pythonhosted.org/packages/25/d0/3ae3af653b5776d045f460582006d0b4d7dd41b078b3ab2e874bf4d1e22f/multidict-7.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:abeec7a89d698aa1c9b4c36bd5e3c746faef0867076e6a2ca27fa5077c4ece26", size = 483160, upload-time = "2026-10-09T20:28:12.892Z" }, + { url = "https://files.pythonhosted.org/packages/56/3e/2c13e111b9f4c6216aee0ac57ac39c53db7f97e42bb8490e60a06eaca352/multidict-7.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:23f6d325241b0db006ca2841309ed17622137e134930a740a8f1331ec4404791", size = 425896, upload-time = "2026-10-09T20:28:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/45/13/15b4d614cf0d6838a7d89eb14f4820fdecfd1b7f529061d172a674912d41/multidict-7.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:034b0dc1b7fb8279599c5d8563f86abb4d2454735b06544ecab23c54572ad2bd", size = 491674, upload-time = "2026-10-09T20:28:16.508Z" }, + { url = "https://files.pythonhosted.org/packages/4f/97/3a6f8c75f12a607ff047e3db9a45dff6a558b1e31b89b2f0f27da0fedb6f/multidict-7.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c8074ad4d67067c87bd0663dfda654f786336078c8fd7d2f6c1aa41de8494cc", size = 473989, upload-time = "2026-10-09T20:28:18.284Z" }, + { url = "https://files.pythonhosted.org/packages/3f/20/5e7726631e50b6afd26324a234851a06104b8ee30f01bc209140788b76c6/multidict-7.1.0-cp313-cp313-win32.whl", hash = "sha256:7b25c335fc53acf29d4d21dbc19fe39d2824201cdda0448623152cc5917bd259", size = 80952, upload-time = "2026-10-09T20:28:20.044Z" }, + { url = "https://files.pythonhosted.org/packages/4f/19/b26bdbef5bf5071ea5a3ba37c963af187fddb8b0a925bd39c0c5dd0ad04a/multidict-7.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:7de54b49e6da811b0321e412d14efdaa1ee0c0b6609296ea5b9022bc5b2bd843", size = 93554, upload-time = "2026-10-09T20:28:21.657Z" }, + { url = "https://files.pythonhosted.org/packages/35/d5/e9a8600d3868e3fdc4a4be1d15e594048508511c0398423df0ed18c11b5a/multidict-7.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:aeba2c750102051aa51e087c2ccbc79f2724a41c94168f8731e36f54c453551a", size = 92039, upload-time = "2026-10-09T20:28:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a7/c9f5c08348a6f903143b631546e22becf1b704a1304b17f7e1b9850308d2/multidict-7.1.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:128ea4142f81a79d430f3d0eb55206093e5eda03a12abbc7b03c34748ff6116b", size = 100435, upload-time = "2026-10-09T20:28:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/82/60/92fe617008c74cc019483b1c59202c48738c6f637ad5b12b36f01e21db43/multidict-7.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:439a19f7fbbff232ce96682c57e27030b8ac3a4b8121484c94f04bf99d08bfff", size = 95267, upload-time = "2026-10-09T20:28:27.856Z" }, + { url = "https://files.pythonhosted.org/packages/17/78/82182f311d673f17de15bc7f7465c7ea5cbd2987ca3a6bf6546fa721e9d8/multidict-7.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8090c35199d6b7bc6426bb8bdaf341e64f295cc2624a1fda7860c0837f1acc03", size = 97233, upload-time = "2026-10-09T20:28:29.817Z" }, + { url = "https://files.pythonhosted.org/packages/63/25/af4e482d053fd73b4b1d60de3ff5578c6cb1d319d39d3f2a454d65409835/multidict-7.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b7cc5333fcbfb27327d12612ed72322f221b61c2b69deb1155078c964f86e1a1", size = 163070, upload-time = "2026-10-09T20:28:31.495Z" }, + { url = "https://files.pythonhosted.org/packages/df/d5/eaed52e199451dac445305fb2631d3f4e7ac9536aeef01f23622a1d93f90/multidict-7.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b0e0040b0d8dd89bd0af9ab18901981e344ffba68bb30b8eabb4eab6c303279b", size = 94582, upload-time = "2026-10-09T20:28:33.263Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e3/ff58ecad5161baa98dec05716e2745449446f2424a9c727464bf452f7fd1/multidict-7.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:33376418ab2846b931a72b36cfa16810befc4f49485d0b3f4dc054a4d6d00038", size = 97406, upload-time = "2026-10-09T20:28:34.981Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a2/ae4eadf02d4bc035baa7cadf5548ca4fa441517193f76ce1aeb5ed276ae3/multidict-7.1.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1101aea5c3eb1d26e090b931c693488af0db9f3d52e68be8d4cdd807dad9841d", size = 449908, upload-time = "2026-10-09T20:28:36.659Z" }, + { url = "https://files.pythonhosted.org/packages/91/55/bd5101ef760d1af4c3f246330b29b48ee22bfdd5a83150b713dd93b431e2/multidict-7.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f996b19ac89e0dae65821ce65f788619e4286f78c62d005ecd3b75b5d9c0892b", size = 471605, upload-time = "2026-10-09T20:28:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/4d/8f/3dea8a28b0416ab71d47c8ce274bb3cacedde2d9838647ac67cdaa3d48e6/multidict-7.1.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e0d91a4bcb59ac0d7af0d8e0da737332e1b7fe6831e53e47819b1b5349d431b2", size = 433523, upload-time = "2026-10-09T20:28:40.913Z" }, + { url = "https://files.pythonhosted.org/packages/24/97/805d2aa4f746cc43cf4b206f1b1bfe2566add95bbb145abd3d9cf61858bf/multidict-7.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e96d67914ddbf5466e4476a1cd7ff30a332cbab85ed895207acc3e58c979b6a7", size = 488277, upload-time = "2026-10-09T20:28:42.851Z" }, + { url = "https://files.pythonhosted.org/packages/dd/7b/eac247b7e76f6010071c7c2401da5255eeffd1b2ac981925e533413d21ec/multidict-7.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:170ba61761f59ab92afcc86ce5534a3f3d0b07c38b339b950a83213f22dd86ec", size = 501888, upload-time = "2026-10-09T20:28:44.75Z" }, + { url = "https://files.pythonhosted.org/packages/87/58/de62e27b09dd15756265765945f68f1c0d1e23eaba09e2e109fbb4112425/multidict-7.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33389fe084e5426d9fd85d7d9ca91a29cd0d88a83c7c96e411aca49a3f9967bc", size = 474883, upload-time = "2026-10-09T20:28:46.704Z" }, + { url = "https://files.pythonhosted.org/packages/21/fd/afb4e50ce3b44707b5ee39c6b5fa1573bdb50c4be660bb35b040362b5170/multidict-7.1.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77024596b9046572c4e90b34c1ff212346756dc48933f90c53cf6e233660788d", size = 424327, upload-time = "2026-10-09T20:28:48.681Z" }, + { url = "https://files.pythonhosted.org/packages/23/70/96c9abf933c4edae53b8a1c8d3f038120a3a60d363f16c6d94eee9b04851/multidict-7.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ff15531a376dc6f35984443fd1429e4b150c36ce27633e7cc52a9e5318546e20", size = 459255, upload-time = "2026-10-09T20:28:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/24/37/dbc1dba26dc1c9e42aa07ee01908131a67fee0e79ebe5c1d0e7fc00aad55/multidict-7.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:544f2642a456fa264614e975d921540ee8c3b368b04d5aa1ddbec33241b13e08", size = 432476, upload-time = "2026-10-09T20:28:52.361Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0a/eac70cc1461668bad8253a2da727670a56293eaba112aeb4079af8cd37d9/multidict-7.1.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08834fb8b20e1a985c70e8380a10940234b4162de62694458727330376e58b33", size = 450030, upload-time = "2026-10-09T20:28:54.252Z" }, + { url = "https://files.pythonhosted.org/packages/23/2d/eb40652ea74f96df859c7c5d38f9997f420b9dc404300dda0c5745327a6f/multidict-7.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b4908e17867930b7ac77f89a18dc67308c67c511f037d8580489be86fb585912", size = 476169, upload-time = "2026-10-09T20:28:56.819Z" }, + { url = "https://files.pythonhosted.org/packages/44/d0/7454ab8335bc4ec9f8abcc4c83a314bab060f22db6da8436b396e07ed21a/multidict-7.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9b24e1f93b9b586ec03bc7bea1bf021ec90bf2528c729195028a3ca1c266b3f9", size = 420459, upload-time = "2026-10-09T20:28:58.744Z" }, + { url = "https://files.pythonhosted.org/packages/60/7a/cedb46b287b720a2c4eda2448faf162612d0b718beb32581e35ccd9219b3/multidict-7.1.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5c93473d0d7cd9bbb370973a9679a62f381c7050d7dff4ad6aaa92e8650f5a79", size = 483071, upload-time = "2026-10-09T20:29:01.114Z" }, + { url = "https://files.pythonhosted.org/packages/9a/11/e7ded22b008546dd30ae8c979aa5ac3282cad98876f7d5bbe93c1f2409a0/multidict-7.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bf14cfcc30b097583d698a6e2b8b68c9bcffab277c485d481881958360c2938d", size = 467470, upload-time = "2026-10-09T20:29:03.388Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f3/0136144cb0b1731fd93475cea75d974a55009358d3bf61dd55e2709a3dca/multidict-7.1.0-cp314-cp314-win32.whl", hash = "sha256:86bc779a0896e59e4be30a5be5cd6eeffd0b40b6f0e75e730218736b7bfc6f5c", size = 80205, upload-time = "2026-10-09T20:29:05.736Z" }, + { url = "https://files.pythonhosted.org/packages/74/d9/a62a690d780febc171026d3e3c5700fc8387aa3e5f77cd04ada33d23d3f4/multidict-7.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c9fd50f636a8fa9cb6324cd3eac962fec2bc5bb432452a3b583583a1059acfc", size = 92923, upload-time = "2026-10-09T20:29:07.337Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/eb7a1c34dc37c5f2c6aef52e6861e6f1cdfc6c685dcb72581eb218c00953/multidict-7.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:e6906aa4bc62cde2c8aeb8a99a7b4401b241e274ae7b11df67d863d61ab3d5de", size = 92851, upload-time = "2026-10-09T20:29:09.15Z" }, + { url = "https://files.pythonhosted.org/packages/ff/3a/019abd746e8fbed9edd74b81259f20b278c011cef918868506d9b5bd6566/multidict-7.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fabfdd4cf97db033196b51af46b8a681d4785c2a66347f2a5af1b4bbb1182629", size = 177429, upload-time = "2026-10-09T20:29:10.989Z" }, + { url = "https://files.pythonhosted.org/packages/f7/41/c8dda935231305d9b83c4a4cc5b5b9e323615328d4704f00ba2cf6d89916/multidict-7.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a177a0ee5cf19931dcaeb3f662bc562754cfa4f4ace2351d9da24a954ef7db94", size = 102003, upload-time = "2026-10-09T20:29:12.78Z" }, + { url = "https://files.pythonhosted.org/packages/af/23/3c3e79a222eaaa59a32648cec34709d848657be5339f96876075731e7f8e/multidict-7.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0ae91de396d5c4ac97cb24dbada3d5c91a51454781e0a70476b008f4e879e4f0", size = 104861, upload-time = "2026-10-09T20:29:14.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5b/04637e7cea5729466fb89048520fde167c5404df1beedcc86f18aeabed7c/multidict-7.1.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:160bdb3520fdadcaa21e1b98aab2e011265070814ecab3804eb61674becbd400", size = 463221, upload-time = "2026-10-09T20:29:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/2d/42/b121767de213a9774399c200cb877b19c2e1e5a0b0a6bc8407c7325fa37e/multidict-7.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c2144785e42527404bbd5cfd11981fee4abe59a22aded0e498eb711a831d3f3", size = 502241, upload-time = "2026-10-09T20:29:18.657Z" }, + { url = "https://files.pythonhosted.org/packages/47/0a/1cccd17ebf39776df7d8a6c99d066fc3ac1823d044cfe86a22ebbf42b841/multidict-7.1.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d0b4fec6a8d02d7e95de5cfa913261820f1ce04bd4c0381924de0da523179b8", size = 457128, upload-time = "2026-10-09T20:29:20.589Z" }, + { url = "https://files.pythonhosted.org/packages/19/a9/0616d20dee16255f8737d9017288d9304dabf3695ac0071baca25d00c713/multidict-7.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:943a9bce22180ad0f4d32d1b402a0949a4ecfe5a1257b47f54a1b51981d81b86", size = 511842, upload-time = "2026-10-09T20:29:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/91/68/5a406b82ecfeee50c097c05fd519c836a61815d81c4a9a8c523d90fb4b35/multidict-7.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f76ceb623f7ff50df46ac57e1587c479d87a5766319c4f43d0c0a5158896afab", size = 519852, upload-time = "2026-10-09T20:29:24.527Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/d42234f9d4f0e33885c70149112bf62aa4436045090c15cec0810ada0b2f/multidict-7.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98beff85392ce435b28a0971ec21cade61ce8be8b632c9d855475a28ef92d31a", size = 500322, upload-time = "2026-10-09T20:29:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c6/323e921a9812a6ea82978d4e1d9713e0b709023be037e6e4c14efe5472eb/multidict-7.1.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:71196ebb8d523148e5975396a444de02367f204b53b14e26794c96b2be0ed742", size = 444070, upload-time = "2026-10-09T20:29:28.52Z" }, + { url = "https://files.pythonhosted.org/packages/93/87/109b7170de2168294f3e562f9d10caaaf946628d614b5a57246279acc1a6/multidict-7.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:35534b366410a36bb3d6f788691e37a76e4d1da48326b0ada3e5032580dd76af", size = 484862, upload-time = "2026-10-09T20:29:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/7f1f63c9cf13f47036c0c64c607e6eee87c94076e3c6876abb6990ee62ad/multidict-7.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4674b12701c3fcbdf7f88b9e4479701c93bec5da9eb576140d5fcc0092990af", size = 457314, upload-time = "2026-10-09T20:29:32.6Z" }, + { url = "https://files.pythonhosted.org/packages/93/ce/7de8b4ee6a847cc951915c279fc388127ec32a7c14aa7a01c4ad692be575/multidict-7.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ead852a5e906a43fcb6784eeac480f6a67919a51d480c1f80d32ddf9d615475", size = 462672, upload-time = "2026-10-09T20:29:34.776Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/02dd089475983b1f5b8729319caa0b02fa3a60b527f27c86b2d53c24ca11/multidict-7.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:afe36ca503c2ffe30fb6df82b20389fa3c4035b5d65888a61310921cf3ae91c5", size = 498109, upload-time = "2026-10-09T20:29:36.763Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/b9bdc59aa8e3eaf1f9e5d2460bbe0c428a01250d44417f8ccf965a8f6fa6/multidict-7.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a5f0bebb10aae010d3c9ee3abaf83ab2069c718457aea09c15532355dd7e061f", size = 442260, upload-time = "2026-10-09T20:29:38.872Z" }, + { url = "https://files.pythonhosted.org/packages/fa/83/21b044885ab81bf5c03ec5c4c16aa449977e428c5c122e38969481cec1df/multidict-7.1.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:b9d9b7d72975521434368fe8aed3f6b522060bf271adabaa5ca6c87c0c08e168", size = 498916, upload-time = "2026-10-09T20:29:41.091Z" }, + { url = "https://files.pythonhosted.org/packages/95/8b/31686730092e349ee2255fa630945ca344fc62e76f0b18d65b0f3dbb0ecf/multidict-7.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:274023bf952f849e0d05eba28a4c1f65f9796430d2b09ec16539386c0f76554c", size = 493525, upload-time = "2026-10-09T20:29:43.192Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d0/c9fddcd7ac42b70a46ac9cf15e21eb527874164e14d418fcb50ce7190a4b/multidict-7.1.0-cp314-cp314t-win32.whl", hash = "sha256:7e0bfa161df365ba3c88899ee3b7c94755200967284bdedef8c1b8b43e2c0f2b", size = 86481, upload-time = "2026-10-09T20:29:45.188Z" }, + { url = "https://files.pythonhosted.org/packages/24/ab/ce727c06680e72b5d381caa8587f561cb6fea1bfab6ba20c877019768a6f/multidict-7.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:34a35be8fb82d37087e8176aba907b9459f03d0e293c80f574c6337a436f4eaa", size = 99676, upload-time = "2026-10-09T20:29:47.441Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e7/d116d7ce514d04d9bf63774ed55e8033e2ce15ab5655a597bb947c53dfb8/multidict-7.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:d7dd46a8fcd7653c09ebe67eae9d4cb6636c7a905d9cbaf587dabcbd4eca6013", size = 98726, upload-time = "2026-10-09T20:29:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/28/42/963e2e38ffd79487b24775841cfe2abb85aa1fa08ee152afc4a9b64e1cdd/multidict-7.1.0-cp315-cp315-android_24_x86_64.whl", hash = "sha256:852c921217f330b3e81a822647ebadeae7e42cf503ec1992d0bfbc90121c09fb", size = 100450, upload-time = "2026-10-09T20:29:51.486Z" }, + { url = "https://files.pythonhosted.org/packages/b3/6e/fbc4721aa0eaea2dff3274ff44e94f60a89841bbb4cdaecd9faa4eb64aa7/multidict-7.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cbec738d2ad551c6f70955d7eec95e339380ee1564e2afe86bfee05fed52ceec", size = 95440, upload-time = "2026-10-09T20:29:53.929Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/045aee749b599560d5348bb1bee5c9d61d8b12f31d16e0594778ef3472de/multidict-7.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a5f721a2437390ab69c10c6df5c142478d399af8dfb02e6d823cf2358e8a4748", size = 97496, upload-time = "2026-10-09T20:29:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/97/79/f2916b81324d629eba363f760fba26cafef5cb437489c24969ada979508d/multidict-7.1.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:99cf27791129d37e191ff013bfc29bf6631c29edb21680c00978567b91fc5d6b", size = 163241, upload-time = "2026-10-09T20:29:57.813Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f6/2aab2b2d7bf2d69204bde0cdabfefe4df3d1954f3f156bfc53714917e757/multidict-7.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2ba6611fc93c4b169d0e0ea376ebf4b8a529933d1f5f2c2ec7d8f8b93ef58ec2", size = 94586, upload-time = "2026-10-09T20:30:00.201Z" }, + { url = "https://files.pythonhosted.org/packages/b6/83/cf690ab80a0db0f50b300a7141f45dbf1bbbb6b32457f74bb55f47d38f5c/multidict-7.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:c45629c0049fbdef932dbe408ac2b271fdc8c7d9962ca31160f4a0fc3455fe4f", size = 97573, upload-time = "2026-10-09T20:30:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2e/951c1412c9803e8f87c8673305be1e448761c940cd867ccfdd3e6a551dfd/multidict-7.1.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cf606cfe3f67984b4064ac605d71e1eba12515fbabf5bd5a34a8952b8800dc66", size = 453476, upload-time = "2026-10-09T20:30:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/a1/55/12f9f95fc65470bf481ad3d1c0e0fc20094720d0af1985119656f2b4be25/multidict-7.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50fdfcb03be719d9573597b095b1175d2e9d0b30d065791dfd9fca727c499442", size = 473078, upload-time = "2026-10-09T20:30:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/87/31/ebe216194a68934de7053d582cde62c839bef4dbc4ae67e9bbccf80a61d2/multidict-7.1.0-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a8bba9d1f6db4ef2a6ebfc937a65d36e80e3aada00b382eaf56fea8f639322d5", size = 435633, upload-time = "2026-10-09T20:30:09.021Z" }, + { url = "https://files.pythonhosted.org/packages/46/e5/4aea764cd6a1d326d91f2bb92b1c8c39c3f5a8da624e7b20b88179dffb72/multidict-7.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46d4af0afc6eb9867b3ae50605787c80b868e2f52eac3801246034925fe578b8", size = 488656, upload-time = "2026-10-09T20:30:11.119Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f8/b1e5935233fa4b504b1a58b584fcdf03526635e4b5f268f716be9108a1a4/multidict-7.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5cc58ebb731200ddb64d55f1b345630fb5f7a8138cdbd242af9dce964a7cb03d", size = 502311, upload-time = "2026-10-09T20:30:13.396Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8e/cde07147b6fc56b9ae0fe01d9c0bebd730b2be2c3052d3a5a117e13f561e/multidict-7.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b87ad54e8d4adeb0a1f04889504d6ec7f04fb02609220810f51f1b6c66bc1cc", size = 477470, upload-time = "2026-10-09T20:30:15.894Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/11ac1880eacddcf698aa227ba9387cda60128e38034eda182cf4585109ee/multidict-7.1.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f7e5dd83a615636b80182bdf446ece57ed61d5d51854acc5d9840631136d4e", size = 430088, upload-time = "2026-10-09T20:30:18.972Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5a/09926de0ec910704507a21c4f1ad76026aab9a5657ced868c57bbbe9324b/multidict-7.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:9bc5e7f843d14a167cdc26fe2d22f6f3aa2feb57919cf3ff034262a57d8d95d0", size = 460632, upload-time = "2026-10-09T20:30:21.45Z" }, + { url = "https://files.pythonhosted.org/packages/ed/36/cc51eb3ffb09f475326bbf868c6af2805fbbe9175122ce6d7a65aa06d6c1/multidict-7.1.0-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:0631eb5f49f67de10bbdc3f64141326dbc62e8d319900966648381ce0845d8ca", size = 434056, upload-time = "2026-10-09T20:30:23.679Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f4/9e0e4dce595573eba1555495ac9ca6b5f7f6e14cf6c3a5cd87ce5bd33d6f/multidict-7.1.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:d1b1b32f3c32f734dde8f36ac1df8e275e768a7b333241cd637cb2538628a4b4", size = 453014, upload-time = "2026-10-09T20:30:26.192Z" }, + { url = "https://files.pythonhosted.org/packages/96/14/993a7ac9f3592f5628a12f6bc2495f5cce5814b883170e06f487dede6969/multidict-7.1.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:159976f9c40f96e3fe0952b708846a43a76bacb114e9cc828816f5080bddd5ec", size = 476343, upload-time = "2026-10-09T20:30:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/c2147c1e225a676ee0c97f97bb935fb8a70821bc12df240491bf9f140801/multidict-7.1.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:81a0e08c64dfdad27dab687b96f572b23bafa1999a39d1b6f70b3ddbb73e8bd0", size = 426813, upload-time = "2026-10-09T20:30:31.015Z" }, + { url = "https://files.pythonhosted.org/packages/5f/fa/8858b260a6662035670759e7b3a7da9decc227296491749073e76202de93/multidict-7.1.0-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:4e11e7299079718c78f8147e7206c22fe35bab4466d38992420795288a0b8096", size = 483882, upload-time = "2026-10-09T20:30:33.509Z" }, + { url = "https://files.pythonhosted.org/packages/c2/43/d42dc515d56d506f437e8a19c4f604719a306f8d2b60f70a14be34ac9231/multidict-7.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:34d2ee98e15d5cfe782a431bc913fce3b58cf3fdb34fcb437aeb275cdf9007ab", size = 469953, upload-time = "2026-10-09T20:30:35.996Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/1b6fb52f7e082b4d49b069d362169a2e6dbc43800f152f33c8f0e8933117/multidict-7.1.0-cp315-cp315-win32.whl", hash = "sha256:f376224572d1f5da1c871f969ab04765727f180e70d012d93e07bfc08442c64b", size = 80202, upload-time = "2026-10-09T20:30:38.121Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/5dec513dec950825529037703da89a597dc0b157e7b1b8d0304dd9e672cc/multidict-7.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:67fcf28db77b385820881521db7435e9f1c607cfaf07db6eb78aa9d1146bde86", size = 92921, upload-time = "2026-10-09T20:30:40.507Z" }, + { url = "https://files.pythonhosted.org/packages/dd/eb/fd62025f8cd3dad6f936df8bd7ff2642e2fe3fe0166ef2fc75699ee57cac/multidict-7.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:c7aafa4dd2f702ee2198005d6cba4309c1e25ed1c201d77beddefa47411bead8", size = 92848, upload-time = "2026-10-09T20:30:43.026Z" }, + { url = "https://files.pythonhosted.org/packages/42/f4/2dcd45731d2f57b87211a1dab1c29a72c6a4a527f370851c3e4cef5c4445/multidict-7.1.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1348ddc076251cd542f4a99ccda4b7c1f8444e8ab489d3541a978ca5901c7c1f", size = 177634, upload-time = "2026-10-09T20:30:45.125Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d6/d57b12a5bb19396a99ae66ec48ff69f764cc2c83e83bd9a003bd5ed5802a/multidict-7.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:9267bf8261a779abb2a6eab5f107f5db85b2d1745f2494081c731aaf28738ce3", size = 101954, upload-time = "2026-10-09T20:30:47.416Z" }, + { url = "https://files.pythonhosted.org/packages/46/8b/374e3f6e4581b8f8425712214cad854ff9370019483aaa49e6207c160adf/multidict-7.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:8a844b8b1685f38a2e8b2f3213b286e2a7abfe67508381780a0d4599ac337c1c", size = 105056, upload-time = "2026-10-09T20:30:49.618Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0a/6c89e2179a84eaa78eec658b9a05f10d3e22654708d7b2e3ebdf0faa0614/multidict-7.1.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:18a447d46a3a2f1e61b365cbf5627db7030fdb707dad70c4f2760e5144166ecc", size = 467374, upload-time = "2026-10-09T20:30:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/c5/62/431ed831b5e9b94c75ed0a410786d07176e879603601570eb25594039200/multidict-7.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88811f890db240a1c82bf0bcd52973763707a552c8113ac3fcebca183afb2fa8", size = 504024, upload-time = "2026-10-09T20:30:54.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dc/52e4204538a7824e407e531b742ff6f7a2ff614dd40382d29a62755699cb/multidict-7.1.0-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a60b720c329c0007feae692b7bf91cf17b3f9bd3727be96cc6f9a3336651041b", size = 460139, upload-time = "2026-10-09T20:30:57.193Z" }, + { url = "https://files.pythonhosted.org/packages/22/4c/0228d61a8fee69a7ac4d8d0426aa1810b9c36b679a8c51844de701230073/multidict-7.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b117ed1cd1a23df0902461c38093408b95971833dcee629112acda25b603c8d0", size = 513162, upload-time = "2026-10-09T20:30:59.662Z" }, + { url = "https://files.pythonhosted.org/packages/56/50/a0b28bf4f036897bbd44c8761fbb384c014c34f621687799d07345b62820/multidict-7.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bbcae7a54050b7ad7bc7bf425ba63dea7d2cd31a92246ba787a2ce69a9b98dbc", size = 520312, upload-time = "2026-10-09T20:31:02.515Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1d/ba96f77c24174eff9f6521cc7c83958f27598b924a9f7b6aca5d11c73f35/multidict-7.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4cba2b0b9235fe10e12301d6b4cfba0f353fa668d635f6e988b03623c2cd42ba", size = 502953, upload-time = "2026-10-09T20:31:05.187Z" }, + { url = "https://files.pythonhosted.org/packages/59/f2/14c3ffb649cf45a629ec38ca757a493f0caf729e3f3580816dc7779e4caa/multidict-7.1.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:535173fbcc3933d84f9929d49d7a59a0faec259ee07d07c34c7d2a980b4e3683", size = 451497, upload-time = "2026-10-09T20:31:07.597Z" }, + { url = "https://files.pythonhosted.org/packages/55/df/fa4f8f6ee2314bdd3e4bc830c25f1d8a737188198abeaf105c73454f0b95/multidict-7.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ea027bdeca1d7e498237634ee4e3a852e2723eef39996dec0ff0f77dff8a2336", size = 486212, upload-time = "2026-10-09T20:31:10.178Z" }, + { url = "https://files.pythonhosted.org/packages/32/5e/3227762b04a8ff1a90359ee2c04af9ed095fe3892e101705fa4233b51514/multidict-7.1.0-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:507151e1e3dee95e9e8159e329aed4f75aa5205ecd6505a4f6be546890eafbe1", size = 459864, upload-time = "2026-10-09T20:31:13.044Z" }, + { url = "https://files.pythonhosted.org/packages/5e/04/58524c7e82176438a48704a687fb383cc943dd7075c67b5bea288043b504/multidict-7.1.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9c10791e9f5ef132effc8fdce2009482c1cfb26618c5fc1b7952a47dd5eb632e", size = 465861, upload-time = "2026-10-09T20:31:15.503Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d6/28e549b6ec0c829647de4c75cf32e4c7ea0d0e87b91dfb399292f630c365/multidict-7.1.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f16ac8af2804855d3cae5fc3c5ab609c9fd0fc8ecacd92579c05ed3c173396fd", size = 498865, upload-time = "2026-10-09T20:31:18.548Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ac/3006640586b4d33442c53989b78fc290c8eae5fab5b62a31ac7a4c220e69/multidict-7.1.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b78de22bae456a976f33df34d598dfd16edc9a03df8f4cc8b7c17bdba4c97b4a", size = 449885, upload-time = "2026-10-09T20:31:21.03Z" }, + { url = "https://files.pythonhosted.org/packages/80/7e/1ece407cee9f9aff9ace9e44379ed937bccb790936ae448feb79a82c362e/multidict-7.1.0-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:db77888081431aaa69f3fd3480891746ddce6c2a571f6201869a24e2f06cf423", size = 500237, upload-time = "2026-10-09T20:31:23.611Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/c052b700ffcf689454848ee81ba87bedfdc10caff592c0d2a327c43939a5/multidict-7.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c39dfcaa0bf23443474c0cb58d8d8aea9529c1841d99654cb38e4dada7b1948a", size = 495143, upload-time = "2026-10-09T20:31:26.237Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ef/9ddad94c32fb0bdfc5ad0942d06ae6699bd30d261b0ff59b94810a133238/multidict-7.1.0-cp315-cp315t-win32.whl", hash = "sha256:16b21164797bde6f417066d02775975cc2e15ab8abf80efa55fe85e0b4894020", size = 86526, upload-time = "2026-10-09T20:31:28.734Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ce/2efa1f32a07adfe041d48c6d04c08b6f9db66b8512c765983172399b2855/multidict-7.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:55392202cb374dd1a1f89a8ce1586644870d9e936752059d053e576acc50bc89", size = 99671, upload-time = "2026-10-09T20:31:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b3/a44c301fe13716c5f2c08afd89a481018988cde4b337b36e28a2b0fcf3a3/multidict-7.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:f979a077d1c0a9a36dd4fab0d3a36b8de7b593bf935e13df85a380395b2c11ad", size = 98893, upload-time = "2026-10-09T20:31:33.62Z" }, + { url = "https://files.pythonhosted.org/packages/d0/86/a3de309c5e28ee85b314d0e3ba0e0dea6fd361c313322a05e67be4656e1e/multidict-7.1.0-py3-none-any.whl", hash = "sha256:d9ef29cfd98e17085b4f91bba8fa1570bec6787d5c52ce653ed33a58785585d0", size = 28087, upload-time = "2026-10-09T20:31:35.945Z" }, +] + +[[package]] +name = "openai" +version = "2.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/9a/8c75e8c8a5b407a0586faeb2afac91674ff955c191ecc1d6d3b6669f6788/openai-2.54.0.tar.gz", hash = "sha256:e3e6f8bc1ba30ddf381ace1a14340eed381cb984a1a59bd0f34b5be3b5d49cfa", size = 1100285, upload-time = "2026-08-11T18:46:59.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/a8/bb76c7356de8ad57f59d5ff993d434df0607f07f08bcc9c9a5c275e399c0/openai-2.54.0-py3-none-any.whl", hash = "sha256:89089789197ccdb87f173a03145ed1598d00795220c93e96cf712b1cbf5e5f2b", size = 1660351, upload-time = "2026-08-11T18:46:56.684Z" }, +] + +[[package]] +name = "opensandbox" +version = "0.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/2a/ab3cc141e041f71a373c97fcda8749dba9328f1b9bf80401378c0611556f/opensandbox-0.1.9.tar.gz", hash = "sha256:670fbf292c498f8467963d21e91ade9ea8b8f63f4ef18d18fff9581e0952ec03", size = 160034, upload-time = "2026-05-12T12:27:20.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/9b/553f8d7a30eddb12785711b2a1c682386878e2bb95450acd806f9fa62930/opensandbox-0.1.9-py3-none-any.whl", hash = "sha256:17faed35b60a982fee5a643fed8e4e12f041e5432d5ea0665d2828d1f2082759", size = 360945, upload-time = "2026-05-12T12:27:19.465Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.45.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, ] [[package]] name = "packaging" version = "26.3" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/packaging/26.3/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79" } +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 = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/e6/d0abbb256b44e6a05748c6942511ff67db8dbb068de0c35b24bf1b395b09/platformdirs-4.13.5.tar.gz", hash = "sha256:05831adf87d392760f4b5c3a0a95579ab5a10b18b9b9bd43680dcb47544788a0", size = 62362, upload-time = "2026-10-19T01:02:11.778Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/packaging/26.3/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c" }, + { url = "https://files.pythonhosted.org/packages/6c/8a/1d8cb3842909dc193a73835adedf412e32e18f8ffd59f43983949622fe6f/platformdirs-4.13.5-py3-none-any.whl", hash = "sha256:53d3ec4280b815c1e0b02edad7f9ee8fda1ba9c0865dd9c2e6e63849ce82fc1e", size = 32779, upload-time = "2026-10-19T01:02:10.17Z" }, ] [[package]] name = "pluggy" version = "1.6.0" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pluggy/1.6.0/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pluggy/1.6.0/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "postgrest" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, + { name = "httpx", extra = ["http2"] }, + { name = "pydantic" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/63/bbd2229c0081685aad8eb713b5bbca91c83dd6afebd545fb63282cb6ed9f/postgrest-2.33.0.tar.gz", hash = "sha256:8813be05d129e8fedc1a9649487ffe524242c33ed380e187edcd16c414915217", size = 15165, upload-time = "2026-10-15T19:14:17.763Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/65/41a44b64da5b4d9c8bb1e6bbc6a0a71ff9315236438cc5c63bf504ececf9/postgrest-2.33.0-py3-none-any.whl", hash = "sha256:802bb30028b897f929e8b0b64db20d949b0c1c58e5804bcc7da02faebf2c03ae", size = 23870, upload-time = "2026-10-15T19:14:16.659Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/9a/9fbf4e4ec0c2d7f1c32519fff782ef467859b8faa9fbc5331a96f6395d43/propcache-0.5.4.tar.gz", hash = "sha256:ff6b113f50bc066a698db5d944d2c6dc7507168dd3341e255a8892fd0715a558", size = 61545, upload-time = "2026-09-16T00:17:14.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/997704118aea215cc5f65c277f5323657b4ba44c1f9a32fb11c8064e4997/propcache-0.5.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b77c313314524ca9c38fbd70f73515d04597ac58c40c939bc0e71eeb4abff680", size = 87225, upload-time = "2026-09-16T00:13:43.864Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6d/ceeca1762ed51230c08d557591404f844ecef5e294550136155572f7faae/propcache-0.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8f911c395cef73c510bac566da9507bb6a43e7763d0c79138dc60ee53f11207e", size = 50809, upload-time = "2026-09-16T00:13:45.33Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9a/6be90814d8762952594a9e380161802541bff690f3a2e011dcc28ec193ce/propcache-0.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d83b12902eb8bce151259c86c03ba746600b2d994543de46e370cecf96c452f2", size = 52552, upload-time = "2026-09-16T00:13:46.48Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d1/7fd3ffb5ca0e669a8fbf55d9fbb17c50ff5f44becfefcab27a9b9b67908f/propcache-0.5.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9281e922c072158c91974d4589f1dbe0fee6d467f284c28e463f9f5a4d933f4", size = 226804, upload-time = "2026-09-16T00:13:47.739Z" }, + { url = "https://files.pythonhosted.org/packages/74/87/a3e199c45b26587f073db655d19c30d2144b0f883827ba6ab77acf5c7d45/propcache-0.5.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9f3551b8a35c1df3e7ea4d2d86edee15f0dde1bddd434a71744048683544d0ef", size = 234452, upload-time = "2026-09-16T00:13:48.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/7c/08c15c7df256f94a6b4563f74165c3242fb554cdd1909b661d093c31b976/propcache-0.5.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ec6a85f424afa8d23e0d9a094e5dbb6eda01da91c92b9183cd433768247ffc97", size = 240399, upload-time = "2026-09-16T00:13:50.416Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/5adee15e12a7e314cece4543fbf295b0fe1b504dc78b32dff4c8eb1e29b0/propcache-0.5.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f574e460d1c8a08384a016fdb09ccf3543433263ed6b2f97104f979e64ea57c2", size = 224251, upload-time = "2026-09-16T00:13:51.874Z" }, + { url = "https://files.pythonhosted.org/packages/9d/53/54bd510bb5d473edf66914602885226187218b4e3a5017e9dd80bceea41f/propcache-0.5.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d8e017eeb7482bed34cdb0d61cf2bcfc88d104bbab296a17cd16a6af8aabc70e", size = 202721, upload-time = "2026-09-16T00:13:53.524Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b4/a468700d0526dfb2ca6af5d790125de5894acc7d8ecd99243ecedfa4c4cc/propcache-0.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f273dcf7149a50527c4fd1f55cfe9eac0f60753f5af544b4c9352578e20c0874", size = 219148, upload-time = "2026-09-16T00:13:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d4/bf563e19ac9a5cc47113431337fdf2ee3573859f3f3e0a58e59833e9b75d/propcache-0.5.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:fc2461ecc45f17893f8207e73b46ea8ba93e33630e51cf4af3fbc21d47462b1a", size = 211011, upload-time = "2026-09-16T00:13:56.588Z" }, + { url = "https://files.pythonhosted.org/packages/4b/3d/0189b6537f4a8cd795e685a130a6f47aeda78a5b7b062574705cffc685ca/propcache-0.5.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:279655a16973f1ee2bd2fe79973137681642fd9ae0d89215bba263726eb0dc3a", size = 228148, upload-time = "2026-09-16T00:13:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b7/59c16e245a549df202b4ebe2de91fa58a67dc4373df9840e372a64224dee/propcache-0.5.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e9f165403b81fea7e89c932d89046a1e3d9a3a60e8d7ef2f249dccdcb0982bf5", size = 201432, upload-time = "2026-09-16T00:13:59.453Z" }, + { url = "https://files.pythonhosted.org/packages/41/0b/7b19eb20bb0b1f9476d08f6e387ae094dc6870fc06d459ea1f35f28b25d3/propcache-0.5.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1783582065a1f07f9d9ee1e992e13f15d7dc8fb1eb3a7476d43eb3f2e69d26bb", size = 228923, upload-time = "2026-09-16T00:14:00.731Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f9/b1a0bd47218216b935d019912d6fec1676ae7c07f15fa82bfb54b8d58976/propcache-0.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d605bb239b796e82a81c6709548b2bd460ab73b4590cb0c83de8a2dd9694d0f", size = 218327, upload-time = "2026-09-16T00:14:02.091Z" }, + { url = "https://files.pythonhosted.org/packages/55/c0/51e5d1ad504e9529831606534c48e272db751072eee9ff07e4abe37e50bd/propcache-0.5.4-cp310-cp310-win32.whl", hash = "sha256:141fdbd73748db0cf7636035030aaac383d2efde8f34e7bc24594cc776d225b8", size = 43096, upload-time = "2026-09-16T00:14:03.427Z" }, + { url = "https://files.pythonhosted.org/packages/80/57/0a0b4b1122f4ed5bcd1c626d910003cdf5282c2a12f8a1cfa17970b3ded2/propcache-0.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:146f48a9e4812611a7581003b1a39de56c34967046310c4171a68ef908c9a745", size = 46589, upload-time = "2026-09-16T00:14:04.581Z" }, + { url = "https://files.pythonhosted.org/packages/66/87/e71b24adc8ece61782ba3d6f3879e6a07fdb85bce21a9c529524184e3ec6/propcache-0.5.4-cp310-cp310-win_arm64.whl", hash = "sha256:6c7599df2b57ebeea8de011b5f2f7b85de95e76037d43d34b95e328430275487", size = 43982, upload-time = "2026-09-16T00:14:05.728Z" }, + { url = "https://files.pythonhosted.org/packages/1e/40/14b21e505b7921617466576423f188a5c9caddfdaa1cf4b2b8a83d8fe216/propcache-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:897d1ddf6716e8f47200f7aad9a0efa6cc7586df66c6defa572f9eab379c078e", size = 86393, upload-time = "2026-09-16T00:14:06.9Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4b/5a52e1a7b43563f7d408814194bb23cc8bf214eb6b86639b667a33a8d0d0/propcache-0.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9cbfff4423eef4cc6cafc021469641a2b835f610b2647a6c5281903e21b8670d", size = 50431, upload-time = "2026-09-16T00:14:08.025Z" }, + { url = "https://files.pythonhosted.org/packages/05/cf/b5248180bf056cc76acc60c9c6e8c0ebbfdbd1c6cffd31fd14996927b7c8/propcache-0.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fc24f209c1b7f7f688b66b98293954f5504279760999b58920ee12dd8471c1d", size = 52116, upload-time = "2026-09-16T00:14:09.114Z" }, + { url = "https://files.pythonhosted.org/packages/86/a8/7c6cd6bfead1a11f2e411e688640e6d26574cb0bde7dcaa7423b0b65ed7a/propcache-0.5.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62530ca89187827e4a4fe733f971abe81a7542eeea48ff61995f19b64d7199c8", size = 238729, upload-time = "2026-09-16T00:14:10.357Z" }, + { url = "https://files.pythonhosted.org/packages/5c/b4/442715b2e980df51be52d203549279e027728f24c80b00b5e525e31cd5ea/propcache-0.5.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fc3f7599528db40b1efa0889a620116e2704144495273d66066e8164e45838", size = 246121, upload-time = "2026-09-16T00:14:11.735Z" }, + { url = "https://files.pythonhosted.org/packages/bc/5d/df0684fc2b1732a01a7bec26d7897369022712422d25b09c37ce7dbc88a2/propcache-0.5.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f2d880ff60f45898f4acfa152aac8d04e3ee627d90ff4003491bf92239d5757", size = 251735, upload-time = "2026-09-16T00:14:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/c7/06/519a5ebb48b6f94beb48396e55c905f12246a25c3a3608a7ec7bceabf50e/propcache-0.5.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e9368e87a3efc285e559131092c5db643eb8e56de4ee42064d5baec22ef2bb5", size = 235381, upload-time = "2026-09-16T00:14:14.398Z" }, + { url = "https://files.pythonhosted.org/packages/3c/07/1e0a9bb310830f2245edbd5cd3c6d24a783c053c4efd8e08e386e513c940/propcache-0.5.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:004e685b315646c410771836e72a44f143bbe624f29653a42687815069a303d5", size = 208973, upload-time = "2026-09-16T00:14:15.715Z" }, + { url = "https://files.pythonhosted.org/packages/62/5c/9324fab27d6088eecc47fe4332bf7aaf8c1ded93c36f558391e8a06d41a7/propcache-0.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:594eb4c6ec35e7179b058481f4e9f02521b56de16fa577c4b85c76fb1bf8a9f8", size = 233897, upload-time = "2026-09-16T00:14:17.25Z" }, + { url = "https://files.pythonhosted.org/packages/9c/a3/570d92fc952eae93b676f3a1568f4b89264102abd3c982ab6a9ebec58dcf/propcache-0.5.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2dba2f02d2d5c09ef8a0e6c1a42aeaa451f4be9898cb00b04fe98717da2eb23b", size = 223512, upload-time = "2026-09-16T00:14:18.87Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/26810d889d89bba31db397e6a88f8984af775f5ed6bad0a29dce84324cff/propcache-0.5.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c3ef2818d63bc86071e9d2989ae75a1bc32b8f7059cfd9f5abbbee70c32e2ed6", size = 239043, upload-time = "2026-09-16T00:14:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/89/2d/f9c47691aa024c8299a3afacd78d22a01ab57eb627b481b6089708e71017/propcache-0.5.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:dd2ac8f5b643454c2cc6b6118b13da16e88f4a6434fc3ba61aca384029f04f36", size = 208218, upload-time = "2026-09-16T00:14:21.801Z" }, + { url = "https://files.pythonhosted.org/packages/74/6b/d510c0c378cabbf9d0ac7b663af6d00f2e9074073d93b20c85f24aa5c071/propcache-0.5.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4054acf80d40456a0537f2913b349718649d8d6458a14ab7f48d0ce28c30869d", size = 240301, upload-time = "2026-09-16T00:14:23.121Z" }, + { url = "https://files.pythonhosted.org/packages/3d/80/c80f6adaaa1e51f0db2dce8c9b3714d94ec45e358a21f9a1910b10b40a80/propcache-0.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e94adb1e7d39ff28a8bd8d8b8fbd1df6b9f40976dbe379134f1ce058e532dd", size = 230785, upload-time = "2026-09-16T00:14:24.458Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e2/c32a7df3f39caa7f11b2eb37ea5b6960a6946f2bc7c4b8ff97bbdf6d6b6e/propcache-0.5.4-cp311-cp311-win32.whl", hash = "sha256:9f86f7259efe2c951f43e57d471c9b41daa5bfc7db9f67189059cf1ae6d77fd9", size = 42747, upload-time = "2026-09-16T00:14:25.715Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8a/3db6a3543d8101263b4c52978b6276a04ead2caff2c5ab880d934f47bd89/propcache-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:e904d4d01f36bd6e197590be1533c44e06058771e0746dd073a8ebb3ef880858", size = 46268, upload-time = "2026-09-16T00:14:26.996Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/5222e2665bbf6e45847492ecbf3b9f3e4975a0ae300e5fd465df7d48ce55/propcache-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:d42a9a856a4a6e2f6c10f1318c07e7daa498d6593abe745c71dae4521a26ca39", size = 43547, upload-time = "2026-09-16T00:14:28.143Z" }, + { url = "https://files.pythonhosted.org/packages/71/cd/348d58f142aebc4873345c6b31087629182ca6e0f2b3caeaa528cf882eba/propcache-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b28f41fa3b8c6900457f858ec5b03998f3a6d535fbc1bb2edec5961ea05ec429", size = 87285, upload-time = "2026-09-16T00:14:29.362Z" }, + { url = "https://files.pythonhosted.org/packages/df/f4/f3ffaee281b276da854ac1d7a6a506d26cbc62ea2e623756f1d0a4a1ba1a/propcache-0.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dcbf346a318a5e30063f547630b02bb787ce2f45b6368d5da143660b6a3835d8", size = 50984, upload-time = "2026-09-16T00:14:30.473Z" }, + { url = "https://files.pythonhosted.org/packages/25/88/1d7df7201750b37765ef2b23bc1c526c028dadde80afa0f57a118fc01182/propcache-0.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87a3caecf8095e48dc72f84bfa42e23a848cf410cc9cc13031fba4869b706a21", size = 52460, upload-time = "2026-09-16T00:14:31.692Z" }, + { url = "https://files.pythonhosted.org/packages/83/4f/48865bd02a16ee5236bc46166b2946f37b93e07b0eae355dac0be0b216ca/propcache-0.5.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60a64cbccaa11b7760ce705a14ada17ba459e7ca9f23ba587eb013821032d7ef", size = 251768, upload-time = "2026-09-16T00:14:32.908Z" }, + { url = "https://files.pythonhosted.org/packages/b0/19/3742a5eed62317b03b4002ee865dc9fd720308bdd0da1f29a5786c630311/propcache-0.5.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a74bfa37147cc08fb29df10bd9c16f40fa7f860cd3a6d2fff853323a94f6e17f", size = 257723, upload-time = "2026-09-16T00:14:34.267Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/ee6350fb0be9122bb6c67082a876d34b90d980d100c106af4b81023e04f4/propcache-0.5.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4d7a54719b67338a305dca2ce6aafe366817df94ddfd4b5514374356f5ca546", size = 265597, upload-time = "2026-09-16T00:14:35.56Z" }, + { url = "https://files.pythonhosted.org/packages/85/9f/83a07b6ec0e043c050cfdd35fb0cf1b7897b91d554d6eea293740309afe7/propcache-0.5.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2814ecd8e818f487bee4b0f921bc4d1c176cc5fc71ac0f072d0fa67eda4ac14b", size = 250424, upload-time = "2026-09-16T00:14:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/33/2c/a763a8251f50fba042af0fb1f02bfec4b31381e40aff760db2be7b2e1f84/propcache-0.5.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6af4693716bfb03f1752ef1b30faa593db2c01d5272e9b8564a1549452a979ab", size = 216748, upload-time = "2026-09-16T00:14:38.369Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e2/4d11bea8fd6a777149c6c20645f873952eab5de3a2497aa11648ec9ab6ab/propcache-0.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4fbc1a15dc8cd1689508758d626b372b1f09d28d9577667feaf9e6bfcd8efcbc", size = 246533, upload-time = "2026-09-16T00:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/9f/36/6683597de4907e70c717e3588c541202c66086a72ff3db58be49de66e72c/propcache-0.5.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cdee8205a44d0be91bbac4c41b95d86641b72dfc7aef1279400e4fda3f26a937", size = 238173, upload-time = "2026-09-16T00:14:41.259Z" }, + { url = "https://files.pythonhosted.org/packages/85/84/cb08d79f1762daafeb2b030c470cd0c725c97b8ad67412457c6f35c53e9d/propcache-0.5.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9a2a8a50a93dee0268a860a07fa3b4bd968f8ce4dbd794957da772f395368526", size = 251128, upload-time = "2026-09-16T00:14:42.652Z" }, + { url = "https://files.pythonhosted.org/packages/c2/0d/41b848036db6621370c1f2e5471a7da8149c730f8552a5257567721f4576/propcache-0.5.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7ffafcbfc7b549ab940047e505c831eabac5e67de53e1bc174adbc5285c55944", size = 214821, upload-time = "2026-09-16T00:14:44.112Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/adfae4bf9c63bccf12e2d9690a175c6579047a6eec3b5a6a5f51428c15e2/propcache-0.5.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d1f5a500bfcbb2c0ab85e98a0dcd70f5899d34efe365a0187700369a79603031", size = 254793, upload-time = "2026-09-16T00:14:45.429Z" }, + { url = "https://files.pythonhosted.org/packages/51/6f/eeca9647245d5f92e87d53e5f14335bb42fce1a7e6842c8045b364eded8b/propcache-0.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a235f73d6e020855dc29dff012d920c02ee0feab8d73a24185a7569f4be1161", size = 247134, upload-time = "2026-09-16T00:14:46.976Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a9/424e38838793d37160b4379c702f61c74c598fc6cd17204adbe3c554f7a8/propcache-0.5.4-cp312-cp312-win32.whl", hash = "sha256:b3083bfe87f95c756e610bd8025f26cbd1cd4aaa03a422f2d65efb7a97cd53d8", size = 43073, upload-time = "2026-09-16T00:14:48.338Z" }, + { url = "https://files.pythonhosted.org/packages/58/7b/6e8ef26f6d510a7916064fec68d55fcbfbdf7eb01e377480d66a122152d8/propcache-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:98914de2c4d7f0f9f4a8c6ea4bf05841f4175796941e3ef7d47eb718f22311fb", size = 46190, upload-time = "2026-09-16T00:14:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/08/b9/72028c5b56ced97f456de6aefa79435ca64d7f77af78ea8cf3c76fc5195f/propcache-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:8876b39961e33d912afe3c1bee18ee564fdad0206f873cc15d522756b7f50737", size = 43075, upload-time = "2026-09-16T00:14:51.155Z" }, + { url = "https://files.pythonhosted.org/packages/78/4c/3b1365d58a667689e067e13d055fcd92bdf8d9a2fca3d9201b47ed5b3631/propcache-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:36c0d9db44b523ef93d03341b1c42d69ff01d673c053d1b1c6c3a363bcaa39ba", size = 85290, upload-time = "2026-09-16T00:14:52.342Z" }, + { url = "https://files.pythonhosted.org/packages/8f/61/5f9c29c3aa67c30238c4eadf95149b1d983a48f69b86b0cff927a7d6df13/propcache-0.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1d52a05dc417279f7e5c7618c5dfbbc29923aaf9bc0a5c1802ddcebf54c61a0", size = 50027, upload-time = "2026-09-16T00:14:53.67Z" }, + { url = "https://files.pythonhosted.org/packages/25/7d/c1ab1ef09e9d4d835be5d58c0a32a1e1de8397abaa4e502a9d4141328cad/propcache-0.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44149f46500a0a41b95b4d99c2e586a77319539730607b9892974a092788b111", size = 51425, upload-time = "2026-09-16T00:14:54.826Z" }, + { url = "https://files.pythonhosted.org/packages/73/36/0093091ebb270fcd1bc1f6e095f93b2e0ed7f1011c28837dc2dbe5f96b99/propcache-0.5.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbab5f5ff6897c81f355d079010cdae85b02e5a0b518b5251523b8ad8ae9ac3c", size = 233595, upload-time = "2026-09-16T00:14:56.09Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/0de9d4c8e05ce0be71b436919a216bd7fc5cc6e2691c0602295efb22b9ed/propcache-0.5.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3e98c55bde2bcf7db3c70d1aed7ae9aa8aebbf19a250c66645cde44cdb8b867", size = 240318, upload-time = "2026-09-16T00:14:57.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/2b35e91455209b85ee98f7859583e0814fab57d3af0f2381aaee34c37304/propcache-0.5.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db3ae52ccc150dbc84704e9d642743897f3e1c54742ff34cacb661e52e3818a9", size = 246649, upload-time = "2026-09-16T00:14:59.352Z" }, + { url = "https://files.pythonhosted.org/packages/ed/74/08e6c1faf26ee2732023a3828787ba535557122774f4a386b1f715cbd8e0/propcache-0.5.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f85915e00dcb1cd9f2f890ead064ed40a27df06f0db65be427b29482ae357572", size = 234316, upload-time = "2026-09-16T00:15:00.696Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/08385733c9321c9bb78039d3ff31045e4fca962d9665023c4eb70f998819/propcache-0.5.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c2ba30a89035b57b73e00475de948521602f543d79ce01db10b04b36c4c76fc8", size = 204666, upload-time = "2026-09-16T00:15:02.019Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f4/e87bc7629af9a14a752b218764a78742d73c2c563ac58315da6841f0cbe4/propcache-0.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae58f361bd5dae942717c65d3413b478c70aea9c462599e7b9adad3731db3894", size = 225900, upload-time = "2026-09-16T00:15:03.394Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6d/11014938d3fe9bea2ea2dcf930f26ed565bfb2f5be3c756362ea48c92636/propcache-0.5.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:96f7c5c15656040ddcbc51e56dc59b58aa25999d743c126abd425b9766ab43e9", size = 219988, upload-time = "2026-09-16T00:15:04.811Z" }, + { url = "https://files.pythonhosted.org/packages/dc/72/fbf17c589f92c0b3bbf6709a425661f8ef2ed0d46b38985a7d7b5a0f6b91/propcache-0.5.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7cc528e760a8af06f2b13e9b9f362cd90c7c718ea61228a96dbd31ba16ed7f47", size = 233611, upload-time = "2026-09-16T00:15:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/55/7e/dbd637572a279692e5518d117274a9331bf5faac59f191d30e82521a3ec7/propcache-0.5.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:425f8cc86ab5018b4b8d4a23bc8e74d964bd3d757c3702e301aa79be76c53f6c", size = 204333, upload-time = "2026-09-16T00:15:07.961Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5a/f99c92068f1e0f5c886899ce0e4a619db376ca98c5279d93f95bd86906af/propcache-0.5.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a5793c7698a53f56f4a1889a4737c7eeb1b7ad0842fa6b1abca22913ff79c8c1", size = 235177, upload-time = "2026-09-16T00:15:09.334Z" }, + { url = "https://files.pythonhosted.org/packages/ee/28/95456fabd2daf6be89049a13fbf03341756014d2959c83d12957d4c49694/propcache-0.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c02c0e570c5c7e077b0181a9f3cdb7d4c3617d1cda6b5c95bd5d34022923d82c", size = 228982, upload-time = "2026-09-16T00:15:10.729Z" }, + { url = "https://files.pythonhosted.org/packages/b1/bb/df90f62c9cf7c93ea235f6f9405143bba802914607317266dd81fc8d737e/propcache-0.5.4-cp313-cp313-win32.whl", hash = "sha256:3e413d7a4a9b4866b7a761d6060d434b64d23cd35122eda3b026a0bbe8196b25", size = 42611, upload-time = "2026-09-16T00:15:12.111Z" }, + { url = "https://files.pythonhosted.org/packages/01/bc/e0a7b84af04ec02d73a48aa71f091e1e4a2107e3074b7ce12195b66901f4/propcache-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:0c889f6fa84957bc7e8b4eab71fd16a0455068d5045e3aa40c733071d2b2fd77", size = 45342, upload-time = "2026-09-16T00:15:13.519Z" }, + { url = "https://files.pythonhosted.org/packages/9a/70/50b031cafe72a5c1878b903ee87303f71313345566bf3d6ec202e5ddc9ec/propcache-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:69fc35c0779522da366c563e5faf203ffc1f8ff0021d5b1337fa4efa5be73177", size = 42408, upload-time = "2026-09-16T00:15:14.788Z" }, + { url = "https://files.pythonhosted.org/packages/33/c9/07e227b930c8ae513b8ef1aae3793499be097bffcdf7aee4fb8b33db4cd1/propcache-0.5.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e6720ba44ad7e72174314d0e1fb0172494cff5c73a3a8a2159c3d2402ff15565", size = 85933, upload-time = "2026-09-16T00:15:16.073Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e1/6710bb44510c4e4a8e0f004bbaf3cecfd048141309c77bae56d4e5a6ebc1/propcache-0.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cfe0a92ae30151869e67a4b5f5e105e4e03ad30b3f38e5211b5bf77d0881993", size = 50179, upload-time = "2026-09-16T00:15:17.377Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/b533b493d7025456f44518b33e53e000021a20fe7c27b88cf3d341df7186/propcache-0.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d759d05634f1b038fb625a66662a8c85e5a8fec912da381b5149ddac107482b", size = 51942, upload-time = "2026-09-16T00:15:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/f1/74/70ac8430e28f21e442c7bcb964eb46c4363f6881ade4aa0e978bfd8d503a/propcache-0.5.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:251c63dd46a0659bb875cb254dc4c1e79ee91a847c737cd62373295afc2235dc", size = 232647, upload-time = "2026-09-16T00:15:19.905Z" }, + { url = "https://files.pythonhosted.org/packages/72/95/f222f13b6fe623310be0eb61a673bf26df439ce27e563ca8e422d0818777/propcache-0.5.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a8d5ff04eb1f85698a78d20c62a14676e7b960dcafde09a388d60ad377d355d", size = 241541, upload-time = "2026-09-16T00:15:21.3Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3e/763e370340db16115c5e63ad46e21ef0770a7f06928b3d3b62d8f8edfca4/propcache-0.5.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b9100a93b372418d8688f3f2a3e5b45c64d70ca4d6176e121aca1e3bfc1e32f", size = 245332, upload-time = "2026-09-16T00:15:22.802Z" }, + { url = "https://files.pythonhosted.org/packages/96/d3/e97cd6f5de2176bd90ed4076c7a9b5e09d0f0b9687d00a576507988bb62c/propcache-0.5.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc07876cfb079b6f6f36d21ce75784ad6c2c6b563eeac0ed26c2fa2669b85df9", size = 232757, upload-time = "2026-09-16T00:15:24.374Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4c/6766e5f60bcda26d244333aa71d0a702c1c9b21b251d543c7af5953d1eee/propcache-0.5.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0951315a6b3142ee2167404d707743f0157c110091342b1aa0accac5cf0e4acf", size = 204389, upload-time = "2026-09-16T00:15:25.667Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5e/ec4bb09a70b26ea99d76a8292c3383b960b296de2b347ac9986678f1761c/propcache-0.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bee7d3aed13d56f54e681df38c3a23031bc9e3863f687d9d598825c9146acd7d", size = 228217, upload-time = "2026-09-16T00:15:27.11Z" }, + { url = "https://files.pythonhosted.org/packages/e1/7d/b53922ba7d9e5bf797324e63aa05906ec240871899f779628df068743e2d/propcache-0.5.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4e985382be6d15da8d0c2710a6fa7b9070fc9ecdeefb7f580e88373984ec8be3", size = 216947, upload-time = "2026-09-16T00:15:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/b62eee45e5ea4de094a258cbb3b01c1e856ca51ddfd95b43135c5effd1eb/propcache-0.5.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9e9ab13760aa8b6d0881ae7cb04fd891d8d490cd2554ea8e79bb278399169bcc", size = 233457, upload-time = "2026-09-16T00:15:29.977Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a9/feec61ed296d993db9dd097e0f6723e3f576a647722367547495e4c5b05c/propcache-0.5.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1b2f3bec4261a94019575481c726c29850f72e27907773c75b1de421e20e9f9d", size = 204131, upload-time = "2026-09-16T00:15:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/92/4d/411ef380cddad28dc001f1c6d75ec72c76cd3817030f68ec1ccfba0ec6c1/propcache-0.5.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:720cf832eb2d0b0dfee129cb3335a26f6ce3cc45ee1187e8f0731758caa16792", size = 234820, upload-time = "2026-09-16T00:15:33.087Z" }, + { url = "https://files.pythonhosted.org/packages/15/37/c988229753629ef1cfd5198337a83e624780ea2b3787efe9e747c05aad2d/propcache-0.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fb0a5be8d9aa213150e8d8148a42aca4984b285bcad1e69587dc4298edd929b", size = 228350, upload-time = "2026-09-16T00:15:34.533Z" }, + { url = "https://files.pythonhosted.org/packages/12/49/5ef1c5cf98591da3c5b952b39e6a298084cc1ce353bc70f85e82397a5036/propcache-0.5.4-cp314-cp314-win32.whl", hash = "sha256:30cc1cebaf9aef49db06357a50398323ae04d70460c0491837d026ab7d6452ea", size = 43578, upload-time = "2026-09-16T00:15:35.957Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9e/a0ac821a2229186af5e2e3c3635a78abb23cfddca57f38513ab5d70420f3/propcache-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a095db8e15a6020db149ecbed6461939fe74f6acaa3ae8b702a1fe8c38cd983", size = 46304, upload-time = "2026-09-16T00:15:37.655Z" }, + { url = "https://files.pythonhosted.org/packages/a1/19/c8d0d36a9d16cba5dcee67d389c9333b988c8986a653a61c00a451817a46/propcache-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:45488d1a5f9ab5bd90aaa1ca20f50fe1922b8ffad71a2009d2adf41355897aac", size = 43440, upload-time = "2026-09-16T00:15:39.091Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e9/42f1da77cacfc184e6ec929557ef653b7961bbf6f1da460b9221273948b3/propcache-0.5.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:53eaa697c4d0422ff4cb714d00231b43352064d97b944033b30c1d57cc506ec0", size = 90672, upload-time = "2026-09-16T00:15:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/cf/2f/4b79940908c6ab8c795097c102999d7bc1f7e0b8604dfd1c232f9d99d67a/propcache-0.5.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:886b59c4d28ca97dd23b025fdfc50a0356be934efbbbca89ad26230067f86fe5", size = 52586, upload-time = "2026-09-16T00:15:41.575Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/02196ae6320c110235bb343f90dbd34be41f8b8964a3ee30db84ec12579e/propcache-0.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fa15757fea1dfcd5b7745cad9f4638929605531bd4018ab2adff7955f1a403d", size = 54335, upload-time = "2026-09-16T00:15:43.027Z" }, + { url = "https://files.pythonhosted.org/packages/6f/44/f48b9a131985659924df5fa5093f68fe72c7ee375329802989ba3126efc6/propcache-0.5.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f0093ac3e9daada202c2082439d414a625c57184727a46e112a3fb2a81cb788", size = 297567, upload-time = "2026-09-16T00:15:44.373Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/418d956d2735139f77fc35262179f1f52c23aa666de5a8ab3819c1ae7854/propcache-0.5.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3cd3a7edb6b95b9b33998135ebfa18d709da82290fb8f27c858970b5a12c8b56", size = 297477, upload-time = "2026-09-16T00:15:46.048Z" }, + { url = "https://files.pythonhosted.org/packages/69/fd/ff811fdb6d3d3e67fd9bbfb75881675d34a42d0ef29a45d33e3e233dde07/propcache-0.5.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c174bfd1c48a1b51a3078e95586dde718374bac79719ab3541ec9e74aec40574", size = 302669, upload-time = "2026-09-16T00:15:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/fc/57/527910c455b5ec62f6871bef45d4f79fea16cb8c966ba0d4a07f0339ddc4/propcache-0.5.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a219f0ac59817a9114dd2aa57c13180f993e819ba658c7ddab4b66ed1ee0d370", size = 287908, upload-time = "2026-09-16T00:15:48.99Z" }, + { url = "https://files.pythonhosted.org/packages/1d/86/f69ab82707534a0cb2057bdca04f9200a71214c7551800f9d34d6ac39e4f/propcache-0.5.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:17a7400cec0256f0a71ae71f9da398f9894c956ff6668a1c9d317b3367316320", size = 249804, upload-time = "2026-09-16T00:15:50.486Z" }, + { url = "https://files.pythonhosted.org/packages/27/19/60677af50d93be4256213de7cd487f056944c048b9c0b6f2e45b3a30f666/propcache-0.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:978f28401afbc76cdc3df9e1717b4229a06b626a1dcc75db4e1f2beb3884c3e9", size = 282344, upload-time = "2026-09-16T00:15:52.029Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f7/a0057808a91fb3b6a5f3602b528f0cdcb3d53e0ff8315d73fabdfdf8fec4/propcache-0.5.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4a1f4f5ffa55dce6307631f3cb2948e117e665966ea512e0d502b16c24f567e7", size = 270167, upload-time = "2026-09-16T00:15:53.466Z" }, + { url = "https://files.pythonhosted.org/packages/83/c8/f4a865490df0dc0c8531d4e59ac411cb6dc24bb255d2396a6f1c60a368f4/propcache-0.5.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:213bb68d9ced5cf2bf717b1071bf2b09b4b04c426256f9fe6d054c60318424c4", size = 286551, upload-time = "2026-09-16T00:15:54.995Z" }, + { url = "https://files.pythonhosted.org/packages/b0/67/b4faebde9da4e8173d0e5a30e8cd31335914af7ef350b988f27fec588cfd/propcache-0.5.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:286867fb156488c251a3721766e380ac4495e4fd6b51aaa1403d89ce7f4359d9", size = 249595, upload-time = "2026-09-16T00:15:56.505Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/52e1dd5636e9f5a27f6b5a4b4e2f33c322fd72afe956c397d82523ec4a80/propcache-0.5.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:445ee3bfb46e85838387fb3c536a73cc0b994dc192b004e40e170adc54aa2a7e", size = 286700, upload-time = "2026-09-16T00:15:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0e/30b2b324b93ff31a0bab539c102aae59e84e444031b2742150a7646aa1bb/propcache-0.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:48cb48c5346a97de792254af77715aa2529c2a1ebc5f586aa0aae44a02f1fe57", size = 280500, upload-time = "2026-09-16T00:15:59.487Z" }, + { url = "https://files.pythonhosted.org/packages/64/36/721bb59f682ff060d0c8df64274fca8cd0521b1a54506c2eedaef795b7f5/propcache-0.5.4-cp314-cp314t-win32.whl", hash = "sha256:03b229037d25b801e7af53fd52b9fc49d9439b036fca1e087e02780631adfa97", size = 46121, upload-time = "2026-09-16T00:16:01.349Z" }, + { url = "https://files.pythonhosted.org/packages/c1/86/0b1b80fa1ac3a0aac44e2922a6964fbe9cd52af5eab8fa933bf9e90b030c/propcache-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:8a1fc236528c457cd739c88abe823da851b7ab645d72792f88658114cc340c12", size = 49154, upload-time = "2026-09-16T00:16:02.901Z" }, + { url = "https://files.pythonhosted.org/packages/69/4f/9fe6f05a47cb550c823155052116f710064b6be5c6e8ec4e9faae7e18115/propcache-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:135036c5cfc93864affb0f9af9a27e5d7a71cb7bd745e7b6dbfc2d56cc30e827", size = 46005, upload-time = "2026-09-16T00:16:04.266Z" }, + { url = "https://files.pythonhosted.org/packages/58/25/895a11d1e4c5c2acc6d816e2bece34e02d9dc92f2182ae276cd819e9e804/propcache-0.5.4-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:45bf2e730ab8905d0527fe05a86500f406e64305c34cc81ebe64b4617cab9760", size = 85634, upload-time = "2026-09-16T00:16:05.599Z" }, + { url = "https://files.pythonhosted.org/packages/58/41/c0acd69271de7a1cf439e77d5d60c18575fd09bad56e798b95fa23458ea4/propcache-0.5.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:31eb43ba2edc704ab2ec27815315dd8a19def0fb16215be4cfe8d32fe78ffd51", size = 50084, upload-time = "2026-09-16T00:16:07.384Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1a/ad561f99f90884089e6403b76c220610809429ba868a81a2e7ce115d32e0/propcache-0.5.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:174507f82d3594622acb1dd2dafecf2d899d6d506335494e7107767bf05f3aae", size = 51692, upload-time = "2026-09-16T00:16:08.956Z" }, + { url = "https://files.pythonhosted.org/packages/e9/07/057bdd3a9609ffad59b06239cceee784b047f6c720247bfaa36d2103e138/propcache-0.5.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e337653721d20ead710da33bf44487fbe8a0db8782714b60306481e9f95b51", size = 232947, upload-time = "2026-09-16T00:16:10.466Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dd/d36ad35986718530498a65e45e3713f9f0e6a580f192ef02d2ef7cae9b52/propcache-0.5.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d21d0d2c82bbfeb1677a9711f38df968f9837576102bb4add1bd449d28d88f1", size = 241250, upload-time = "2026-09-16T00:16:12.056Z" }, + { url = "https://files.pythonhosted.org/packages/fb/81/f1459415cdb6c10d46942779de39bb59a77b38e5a76bb1def9227962eb45/propcache-0.5.4-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccf4f7a79e26bb7efb06ecd50c177833b71df05cbc748701372325e6bcc17f6f", size = 245150, upload-time = "2026-09-16T00:16:13.596Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4e/58b9b1460afc97a4c0b17ee89af701c4011d4d7f46470eba3aaff76a8069/propcache-0.5.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23278f808cd81d5ada7184a76606b925fb3389c60e1077b2cd7da7b1fcf0553c", size = 232166, upload-time = "2026-09-16T00:16:15.126Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c6/5a79e0eda3e7b6987d03d8c622ff6d52a42165a12e8418eb37694b9cc4b4/propcache-0.5.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e738ab81179510ce79b2eac9a6ecf47feffd9e76d1c72e403005dddb6e36c06c", size = 206085, upload-time = "2026-09-16T00:16:16.713Z" }, + { url = "https://files.pythonhosted.org/packages/4e/72/940aed42c73f9da345ca2de0f6e835c726498159abca5f1ef14fb0a2af8a/propcache-0.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:a419ee85e654927baabda3929c03c0cc1112bf472ff0dfd6142f4e3a81ca4162", size = 228460, upload-time = "2026-09-16T00:16:18.352Z" }, + { url = "https://files.pythonhosted.org/packages/85/71/3f54e1535c8f323d91ba566044d7c2b39ff6f6a2f1d0bd9071779d07b9b3/propcache-0.5.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:b61805357d966680acf68b3b6d49772631ed9df44ebece10ff1460e117a7da8a", size = 218350, upload-time = "2026-09-16T00:16:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f4/025890cc389ac3ec485ecec607d4a7ca47e15bfa2a465746ab98af602536/propcache-0.5.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:58134228927cee6c047d626c08e60a81be604a20578a12ce752cc5c9a84d4826", size = 233156, upload-time = "2026-09-16T00:16:21.624Z" }, + { url = "https://files.pythonhosted.org/packages/04/29/b39cae08c87c140d3d274f0a2c058cb5588e836175c3309e260b230ab07d/propcache-0.5.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:350b272b2279f4135a64fc0c304a5d08e28a137c9573442c606152446638a831", size = 206206, upload-time = "2026-09-16T00:16:23.204Z" }, + { url = "https://files.pythonhosted.org/packages/18/61/e16462ef18a87247dc9ebbd5c606f46d5ce67e708bd9cc734dd0d9222564/propcache-0.5.4-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:45bebbe252550fec975ba3b62bc6f931643cfd3b5464ef47619cf3fef154e01c", size = 234469, upload-time = "2026-09-16T00:16:24.841Z" }, + { url = "https://files.pythonhosted.org/packages/9f/84/b6a1490922427204fc47df920ed002eec709621de6b79b11592bf45c623a/propcache-0.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:ada748108a43d29b7c328ba7db3755327cd94f028bcc1a7ee3f0addcfacd9c38", size = 227311, upload-time = "2026-09-16T00:16:26.549Z" }, + { url = "https://files.pythonhosted.org/packages/ff/5c/5a59527582e9bcb694b2f08b9894134b65a0f5f79dbff174f054f5f74ed0/propcache-0.5.4-cp315-cp315-win32.whl", hash = "sha256:ee19113bce2f3acd46432050688b70f61acd6857d75abb9ec96341b7e9ced123", size = 43512, upload-time = "2026-09-16T00:16:28.313Z" }, + { url = "https://files.pythonhosted.org/packages/26/07/93cf699ed363681e754d7c3fad587fb09ef6b65618ee193332ad16a68d7b/propcache-0.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ceb3e879afac028f93d272c957814695dc5569e4904262dbee92f6c41bd5e4a3", size = 46264, upload-time = "2026-09-16T00:16:29.751Z" }, + { url = "https://files.pythonhosted.org/packages/65/10/fef04fbdcd44a4a163cb5ff5674599c6d6fdefd64a5a459438f9ad2ba042/propcache-0.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:c83acbce9f2b5e3f5f5eda9e53d2001fed22fcdfef81274a9e02d8fd53b70a30", size = 43395, upload-time = "2026-09-16T00:16:31.5Z" }, + { url = "https://files.pythonhosted.org/packages/70/f6/7e2f4dab0b92ab46111bd48cee9ee1e5f519514c44e3779ede5358d7ada0/propcache-0.5.4-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:a5e8ef588c109725dc713ba69aadcac00a1ef90c2ce9c0a8c7075128f569f47f", size = 89825, upload-time = "2026-09-16T00:16:43.115Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8b/dfeff925cb6ced97ede701d5c6a99998da963c6f2e06abbf879c9dac5b54/propcache-0.5.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:4d86476a935c88963d9b8e1a9a0d38188790e9622169bfbafa173046846709d3", size = 52159, upload-time = "2026-09-16T00:16:44.754Z" }, + { url = "https://files.pythonhosted.org/packages/24/6c/924c810be5b7cf218ef47e707cf06d34adb4e3f3a31e3c24c55c6d945a88/propcache-0.5.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:f5470694918830da62fac9e69133b53d23b736d7070e587b27a4a2be37e08e68", size = 53956, upload-time = "2026-09-16T00:16:46.762Z" }, + { url = "https://files.pythonhosted.org/packages/3f/b6/9ed0a5c939b58b6bed740a05b5d0f919f0b318d03284b4b6d81a0fe8a29a/propcache-0.5.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10ef33a68a61ce317e095fd2e202a592ea92392b90944a78c993f0d9a73ab06c", size = 295235, upload-time = "2026-09-16T00:16:48.577Z" }, + { url = "https://files.pythonhosted.org/packages/5a/eb/5ce886e902a2e781dddf110993d5329458a9b1a8626b876c65e5e25bf413/propcache-0.5.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5cacf3c9efd09df409dc33654dd077e1c245ba8fb747b0f0236ef41b7c49b589", size = 294463, upload-time = "2026-09-16T00:16:50.539Z" }, + { url = "https://files.pythonhosted.org/packages/f2/88/c98f49183ecd3e5b204a556f0ca47baa02c2206a500fe8c7ec1726297b0a/propcache-0.5.4-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:770e8209d018175fc0063936fa9583b6d27e88c5ad31543f3383d66080efdd62", size = 300081, upload-time = "2026-09-16T00:16:52.423Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/c5090f9e6f67cbc30a2b744c7bb0f8006dcba5ec1b0d82f866ae1cc7c5c4/propcache-0.5.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03969626faf0783a592dfa17e28eac06018bd0b44dafae6943d53b92421a7f72", size = 285360, upload-time = "2026-09-16T00:16:54.141Z" }, + { url = "https://files.pythonhosted.org/packages/ac/9c/34a55396910583ed07926669ab309dde2213a2dec05a7e946bb90ad66908/propcache-0.5.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef3b928d9c984322b5c44e6964d8dbc653da87d2d8ee1647fa6da43072e650a9", size = 248014, upload-time = "2026-09-16T00:16:56.062Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b5/c0a142b656093ca397039dd3fe166cbb87c945712b534546514a24cd2611/propcache-0.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:7177c43eddf10a0893c4fec52ebb408fdcd7f7d63962caace9180d8f81b14ece", size = 280662, upload-time = "2026-09-16T00:16:58.044Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/fab2809c2e337fe26becea9648e84d5cef46075c91b826acb13e4f9dd04e/propcache-0.5.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:420162a77f94eb1cf5ef7893f500016dabd548e73de956785a1dd899cc73006a", size = 266149, upload-time = "2026-09-16T00:16:59.702Z" }, + { url = "https://files.pythonhosted.org/packages/3a/11/7ddf336288b2678a5f054f8da2e2bd1a719f5d4b7de714d9c6bd588a2313/propcache-0.5.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:3eb2e820e8e2101407da93f17c57cbb7d225461955fc60105daaba14cd421ee2", size = 283097, upload-time = "2026-09-16T00:17:01.459Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ae/351b1a5225f5473c411d9a612a229ae147cf0cf65c72ad838b87219ea8e8/propcache-0.5.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:13e52b6e0bde97dee98ab66552dbff2931649c96f1ac432eac299fe689ec373b", size = 248160, upload-time = "2026-09-16T00:17:03.298Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d0/7f79f061e30d135bb615c9782c94a74652033d00b49254edbbf35a9165a8/propcache-0.5.4-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:12682126712ddc19b70ff819debbd279e58adf1f0c8f8f8138c18ade2044b284", size = 283036, upload-time = "2026-09-16T00:17:05.238Z" }, + { url = "https://files.pythonhosted.org/packages/53/3c/016f1cad8bf4c428d748cf399b2bac603026fbfd6966e6a5579b5c5b6956/propcache-0.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:3af0c8642b2da4815d86e631232ac8286e17644fad907c19508aa8e7cb4ba8ad", size = 279350, upload-time = "2026-09-16T00:17:06.881Z" }, + { url = "https://files.pythonhosted.org/packages/ea/60/d8f72cb24b412487ed4c397f539117d3b74c3c33dd32020e91fe00a958a8/propcache-0.5.4-cp315-cp315t-win32.whl", hash = "sha256:1df8d8561b21465c5dd56110a01caf897e026d065b4b84e98a488209094272ec", size = 45874, upload-time = "2026-09-16T00:17:08.567Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ef/8bae0a316d406644450522f2f3d44a4e19632f5f3bb60d1d0e6c53842616/propcache-0.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:02c0a34f16889cf800f10f0247a564d8ce6eeab6ffcd7c87198f769067eb8432", size = 48574, upload-time = "2026-09-16T00:17:10.077Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/bcc053f66a97355683884b448198e79580fae8e8fa4d96b9bb01614e9913/propcache-0.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:dc4242ca653c9b30ab51c5f8193323e7bc0928f897ee9103201e59a43abcb72e", size = 45625, upload-time = "2026-09-16T00:17:11.377Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cd/785c64ed382f3f04201870267b02783f63b4678c2acfddc177a3ebcc2727/propcache-0.5.4-py3-none-any.whl", hash = "sha256:62c60aec739ed00124573cce1178138fd690c7676352d67a37328c1cf51d7468", size = 16338, upload-time = "2026-09-16T00:17:13.106Z" }, +] + +[[package]] +name = "pycparser" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/a8/c5fdbeee588bb8ada9458774f43adf1bdd30bd59157055142183e769a024/pycparser-3.11.tar.gz", hash = "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc", size = 113796, upload-time = "2026-10-09T12:56:59.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/11/0e6f11117525ff0eec40ebac3d313376f102df93ca44ad9e893ee85e4f89/pycparser-3.11-py3-none-any.whl", hash = "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", size = 51178, upload-time = "2026-10-09T12:56:58.131Z" }, +] + +[[package]] +name = "pydantic" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/0b/8e10b2e693af8ec54346a14caf36221334775975a747c9ceaa3f8371d96d/pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26", size = 900362, upload-time = "2026-10-11T18:37:55.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/ea/a56b9fe5066f3537b7882f77e9c5dfb26d8c2eefdaed9b5fc73d57b4dc22/pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454", size = 488106, upload-time = "2026-10-11T18:37:53.437Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.50.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/24/af4ec4be49fbc810f35b0bdcacc3d433b56bbfa469fd3bfd4ae116cd9bf1/pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09", size = 513515, upload-time = "2026-10-11T18:35:44.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/db/c9cc34707ba2c23ce90d54e8a4ee9307583ad054b9df2ce570ba550cb576/pydantic_core-2.50.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b281a3b0f0822618fe5e3e0d8a2048b6356b14388505dc9374ccffeb69989713", size = 2154222, upload-time = "2026-10-11T18:31:38.345Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/6963f4ee55b7df7658f5c036c0f90baf8cd3d413f5d8b551f227e8467afc/pydantic_core-2.50.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fa4c8bc12c1354c5550c0c35c1852c8c1901e89e06561724e03f8d0342e1f87", size = 2048126, upload-time = "2026-10-11T18:31:41.061Z" }, + { url = "https://files.pythonhosted.org/packages/bf/be/f75637e222085d29a3076edbc5cfd0dd9b77d145e310ff7dc1150f07558a/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa9de446b793de2beb6fa2d9d0961803126c4e2a99c2f25ab59b9fd6ea125c0", size = 2052460, upload-time = "2026-10-11T18:31:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/3d192ffa39ce3bd0e98e988fb5c055cfbf4af6e6e155a4ca5baa86d1f2cc/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de531ce1e2a3364e8767878b58f4ff728a434b4fde089781fe30b1e08e2396e0", size = 2190750, upload-time = "2026-10-11T18:31:43.572Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/12ea94df5220ab316ee5bb498ebcf710cff2db352653039fb6033f6997d1/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7c58106de36ac6a56314182958de20db8d3a29dfd5db527192cc754e4f8e7fb", size = 2390043, upload-time = "2026-10-11T18:31:44.745Z" }, + { url = "https://files.pythonhosted.org/packages/f6/bc/4fbb6dc90af038628ec824434a9841be9d46854f9267bb658f09e315024d/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b4c3df25bd323bf1d36a648d563cf1fc69d717451569927151bdad7cad07a77", size = 2432397, upload-time = "2026-10-11T18:31:46.186Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1a/21a58791021397d9ca35295e3f4291c16e319155c5904334b89eb07e97e1/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f77ac30b19221cd9bd3fcfa3d4614eff93140d0572ab730cded17b64adca05f3", size = 2160719, upload-time = "2026-10-11T18:31:47.557Z" }, + { url = "https://files.pythonhosted.org/packages/73/6a/d5b2c5876162fd2c444eaed0064b5d9d9d0ef0d31530fc6a7997e8685ad8/pydantic_core-2.50.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:d939de9c82e2126f7f48a7e658f8a85ed46d57662d53f44c49b8895fe94a3eb7", size = 2256164, upload-time = "2026-10-11T18:31:48.797Z" }, + { url = "https://files.pythonhosted.org/packages/af/e8/1a6625d6e99250388dd0ae817c5d75839cd6103e7b790f59e0ffff74955d/pydantic_core-2.50.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:30ddf019d082c117b5d309e5b86710c2a78909907ec1a9381feec3eec02eca0b", size = 2271176, upload-time = "2026-10-11T18:31:49.987Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/bbdd137d7ec41284701dab4d1489ea597d401dff2f5f3e6b41ea872e0b12/pydantic_core-2.50.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ab756b72bd5054e4c7ef3ded331b35786cbd3cf931531a508f79a9537517064", size = 2304637, upload-time = "2026-10-11T18:31:51.251Z" }, + { url = "https://files.pythonhosted.org/packages/49/5d/7bd322e649b41b45130bc00bfa34dc57498c853aa8e6fc9604e33bc7087c/pydantic_core-2.50.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:b087b1c5be7ac687cf22eabfe4b6b608d40df23610651e93611e1f49118baf84", size = 2467374, upload-time = "2026-10-11T18:31:52.587Z" }, + { url = "https://files.pythonhosted.org/packages/53/48/41a91111590eb7ba68a7c64c03139235a4b8349930abcb87931aa80da796/pydantic_core-2.50.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a44101320cfe99432db74237545a63057dc7a88dfe792cbcad0647f2af56cb81", size = 2480832, upload-time = "2026-10-11T18:31:54.06Z" }, + { url = "https://files.pythonhosted.org/packages/2a/02/4ac93a3d3f0ff412df1a36a9619743336fe6eec68c0df8a914163a3d22b4/pydantic_core-2.50.1-cp310-cp310-win32.whl", hash = "sha256:a4aaaa791bdae1c972a7e81765f4f3571c926b8e0b9b6e47346499fb80079665", size = 2123154, upload-time = "2026-10-11T18:31:55.396Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ba/3a7af3dcd480c4739aa82cd9fedee79a5531a7c553346abcf095bd05f0de/pydantic_core-2.50.1-cp310-cp310-win_amd64.whl", hash = "sha256:2eedf82ee4753cdab8e50044c6bd569577eebc3859b11fecf4eb9223761ff966", size = 2179366, upload-time = "2026-10-11T18:31:56.697Z" }, + { url = "https://files.pythonhosted.org/packages/45/1b/ca81811da3d2aea724fb3c4ba6b83f50ab8f210576306ccd601973142d35/pydantic_core-2.50.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c531166c42ea7bdfecc8c50049581f05dd1993b09cc7c52bb36a14e96deaec7d", size = 2152145, upload-time = "2026-10-11T18:31:58.046Z" }, + { url = "https://files.pythonhosted.org/packages/59/20/938ef7e0541af0a314e2f9d63848dbd1656ba2ebfe5ca0469f7ebee00d5e/pydantic_core-2.50.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6d0c2183008c188e19f4906d426b293bdc4f67ab17df8e180fe16cda208fa71", size = 2045761, upload-time = "2026-10-11T18:31:59.35Z" }, + { url = "https://files.pythonhosted.org/packages/2f/4b/456497fe2affa18f49b226e89bde31e8a204751ee343dd9beaac5438123e/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94be440c03fede26969a5ce75468e0e6a9927a1b46d9b679ee8adc1b057b0350", size = 2049557, upload-time = "2026-10-11T18:32:01.121Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/cd5dfe13928f55e236dff3a1bee06721d318ca6c955d9ca993b0a5d09f39/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:36c426eac0af8d1529ff8467e612b933346caec1fdc0d774f78f67a1a11e16c1", size = 2188153, upload-time = "2026-10-11T18:32:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8d/d0b25bc484f8d9136ea76ece69e0b2d24ab370cc2e7bbd1b79e61723b763/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:028e2f212273d4a39b1ec1e0de8166b1165a65fc0f1111452a9d94fc7c625c63", size = 2388308, upload-time = "2026-10-11T18:32:03.767Z" }, + { url = "https://files.pythonhosted.org/packages/c5/64/6513cac7c9f78492abafa01d57a69de1d246bf3085fd7b0330bf14b025b3/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6f0cc1bb9900dc558960894adeb30b0c083366fc1d69b856209fb2ca5c36fe5", size = 2431187, upload-time = "2026-10-11T18:32:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/ed/60/7f2ae89c37e69a3dc0da9ff57b0388c285542c688d05f36767770429de85/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8812592c85d0edf423f10eadcef42716d71e8219085ad9e85b775057b7306133", size = 2157623, upload-time = "2026-10-11T18:32:06.616Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/e3a71373b85a7973099b9a229a4302c7792262717e0b4997e41c01f7dd16/pydantic_core-2.50.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:bbce99252ba3167b2b6277f1829d5bf4b43b754524bddf7f944707c3db7d2253", size = 2254539, upload-time = "2026-10-11T18:32:07.968Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d5/5b620aaf40dc631d911565b64d49d3fa8422027c046a7a96dc4e4921ae98/pydantic_core-2.50.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:476f6ed8e43cd1e0b460920e23571700872b284e77331cb30c4faf459cf48a4b", size = 2267344, upload-time = "2026-10-11T18:32:09.399Z" }, + { url = "https://files.pythonhosted.org/packages/e0/48/f46baf4580d8a56ca4782aa65842e16d3cf39736b29fcce8e9c0521b3422/pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2cbd1b75b09e976ed0d6b6ca297675632ca35df86130088457cdc60ef36970ae", size = 2300877, upload-time = "2026-10-11T18:32:10.903Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9d/e7d28505e6092c1fe075838a4f793ccbe6dd676e44ab16175815ee06cfe0/pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:5958c72adb417c39b12ac87525ac60b0d73315fcdc59e21f44ee4a5e2512c9ef", size = 2464270, upload-time = "2026-10-11T18:32:12.329Z" }, + { url = "https://files.pythonhosted.org/packages/22/b1/3bf63b82efd93ebc9968f9c12fc46037a28bcb452c88e4688f33e5ff7dca/pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f9e8a6c4ab04b78d61f78627370d834eb004b2869dcb28cfffa647b4ea1980", size = 2478653, upload-time = "2026-10-11T18:32:13.735Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/26d1e4b9c2ba6a92b7dc75bbcb99ef44456b7e3af1bced868403667ea7d3/pydantic_core-2.50.1-cp311-cp311-win32.whl", hash = "sha256:4be846f55c9477f5f3ddde8f2ce941137e16862a56d018ed885d422bb6ae02f2", size = 2120246, upload-time = "2026-10-11T18:32:15.591Z" }, + { url = "https://files.pythonhosted.org/packages/24/65/3e875e7991eaecd4d70686293ff0d8a99aa8f93dc295e6a21c3d702912fb/pydantic_core-2.50.1-cp311-cp311-win_amd64.whl", hash = "sha256:0048b6dddc8ef4b64fccaad878bd143b0c3882ea9936279dc11d613f6b7dd1bc", size = 2176332, upload-time = "2026-10-11T18:32:17.153Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5c/58dbd17c61f209d91c70f549eb7a3bbec7023e89ea12dead091462d6d658/pydantic_core-2.50.1-cp311-cp311-win_arm64.whl", hash = "sha256:6a733778df2f7087ec1100ed0b41533e4f3001976e99570fa34f57c66e7f8e3e", size = 2182471, upload-time = "2026-10-11T18:32:18.49Z" }, + { url = "https://files.pythonhosted.org/packages/ee/94/101b49a6c63f99586755690696eae14532772587d9e39e93f7b8d28a49da/pydantic_core-2.50.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:704075d10b74f2f3c6e15407c696d88701df35fc8953f434a431add0d0074db0", size = 2222428, upload-time = "2026-10-11T18:32:20.228Z" }, + { url = "https://files.pythonhosted.org/packages/db/a3/f8b1b39349c33037c73d7a1b794c9c29d8f07d5bd2b74399c1375a529cc6/pydantic_core-2.50.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e8e1d6ce820aa23317e8209a86bd65a540973c12dc7552b48a4f6c8e9926815e", size = 2025779, upload-time = "2026-10-11T18:32:21.723Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2d/862a7d115305cc621358e51ba13f296e0ffdf4fbd984be9f63bc7862730b/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c18db21573bd2c6489f9a544b7499f0df2853958c568e5e783536ee1f690af41", size = 2048429, upload-time = "2026-10-11T18:32:23.3Z" }, + { url = "https://files.pythonhosted.org/packages/c0/7d/fed95d18bf2b5abe2d5ab2bbec4184bfc22b6805994a33ccdae8edffd25a/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb57f304525a5e3c13333b772bf9a473f36326e9c821b2e8e1b2fd36f80ae2c3", size = 2173274, upload-time = "2026-10-11T18:32:24.697Z" }, + { url = "https://files.pythonhosted.org/packages/93/1e/c6f22ceac65dc3d291ff79bea0eb74cbd1de8b128f01fee48af03bcea1e1/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a27c09d86600f1bf2fe3f37e1ae697faf3143931c09322cd799da94deee923b5", size = 2360403, upload-time = "2026-10-11T18:32:26.281Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/190b5b6bfe4c72f265feb657913eb30749193927a5ad7b83cfa7866a25a2/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46b3301d3b5c886f77de7546e47274a5842c622ea2020b8c6524c6b66913b4a6", size = 2439799, upload-time = "2026-10-11T18:32:27.844Z" }, + { url = "https://files.pythonhosted.org/packages/90/24/48cd98388e4ac2c08d0800af36d15413db15e20387f1038123a2c86cb2f6/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ba4e9d8210d941c200431a56b2c0400b131865947903937ed3ec5404307d2e", size = 2160157, upload-time = "2026-10-11T18:32:29.553Z" }, + { url = "https://files.pythonhosted.org/packages/00/86/d909133d0b9979ed14c20f110f21124a0e346caf071bd66ac5af3009062b/pydantic_core-2.50.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:e5faeaee74a57d32b3ab3aebad2e348f06d3ba946fc5d28c1728455f00a3d13a", size = 2229523, upload-time = "2026-10-11T18:32:31.027Z" }, + { url = "https://files.pythonhosted.org/packages/90/31/4b43f08131b2098e73a852fd932063385f3103b33839fd9d1abdb89e673d/pydantic_core-2.50.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3cda0e538208e5d722bbf3698b24f19c0a7d05bc8d5f8a7f9b121ea7fa243d9", size = 2242700, upload-time = "2026-10-11T18:32:32.622Z" }, + { url = "https://files.pythonhosted.org/packages/88/77/de39658569dc7a4c293d110a89629f61145e7538f00d1e4280c28debefd3/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:57f51b31ff826e2859120cf4737c5a758a48d96f3e97da40ccee1796d58078ff", size = 2278452, upload-time = "2026-10-11T18:32:34.165Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/1eeb9e4e791e81210ef9096f58080b32ba15b5b13c108d50aaa3f8ce9f73/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8daa7ee75245d43ad7d747e5c9ecc1b1d06552f72b14887e9276f787d57375f4", size = 2450098, upload-time = "2026-10-11T18:32:35.547Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/b2accfdff17e5a88d6358536702146de00f9339adaa4d5bbe1ee93cb4832/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:acbf31f37c53a5ac0c34706c80b4f5107ba20b05fdd3816124bf236ef0c57dd2", size = 2476839, upload-time = "2026-10-11T18:32:37.119Z" }, + { url = "https://files.pythonhosted.org/packages/3b/7e/c8a83eb5d2cd42c2e13507150fb777b1edea5a018adc9cbf2858a1d08285/pydantic_core-2.50.1-cp312-cp312-win32.whl", hash = "sha256:45b11cac094aa25725581d9304eee93c9028516b9ea80dd9e175e13a5a2c840e", size = 2101425, upload-time = "2026-10-11T18:32:38.593Z" }, + { url = "https://files.pythonhosted.org/packages/c5/17/8bbd530b8659e9d963f5b16f6cb8e159f4cd174b4d6304e48969ab12dba8/pydantic_core-2.50.1-cp312-cp312-win_amd64.whl", hash = "sha256:132529c83901437ff642f585216831bf5fd7a91df66829907e155192ead62498", size = 2172522, upload-time = "2026-10-11T18:32:40.112Z" }, + { url = "https://files.pythonhosted.org/packages/af/9f/a79d690b127e4e3cff143d031538ab9aa715f6b2fbeecbd07cd6f2bf9fb7/pydantic_core-2.50.1-cp312-cp312-win_arm64.whl", hash = "sha256:4e834f6a8e4ff772dcc34f58ef5504147a3ea5b0f4eeb13b0f8eb2ca75ac57f1", size = 2161366, upload-time = "2026-10-11T18:32:41.606Z" }, + { url = "https://files.pythonhosted.org/packages/ae/57/0e237d7091d2cd44a35d243b7344227f90440166860469cd036afc23a04d/pydantic_core-2.50.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d5e062c01286d861fd6a1c4ff6e063547b3e713067f2df033c0ff97ac2ca006b", size = 2222774, upload-time = "2026-10-11T18:32:43.103Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b9/c720e56858d4e1539503297ed37063e0c08e0f3541c41b777f6a800f75fa/pydantic_core-2.50.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c003c3b7f49debb893d2d85ae099ac5959c9839e2f330fadb1fcdf7a6594482", size = 2024738, upload-time = "2026-10-11T18:32:44.587Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b8/fbfc25875219cc060e613170ff10e850c6d8924beb4910da57c2ee3ba1d2/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:409e0ea40ec30d9158f33574fd758e689f6045a0f2596701828c27816ca9687d", size = 2047049, upload-time = "2026-10-11T18:32:46.164Z" }, + { url = "https://files.pythonhosted.org/packages/d7/43/34210124d504c553688f2f04b48500b131237528ac545b445e0d6d30e0d5/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:131059670f1d2444269b8585cb888963994871932447c08b39ac6a51fcfef658", size = 2173415, upload-time = "2026-10-11T18:32:47.722Z" }, + { url = "https://files.pythonhosted.org/packages/65/cf/6e178e8fdc11da5965bef980983bf46326a42f436871dd51ba0a57f39df1/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dbcbee53bf17196a7f745aa9bf5a9603953a1e365b1f020be3207c676a3e7c4", size = 2361719, upload-time = "2026-10-11T18:32:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/11/14/bd5169d356aa91bf777e28ba0b281c192d286d03c2812c9c9db023a2f00e/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:325c23f3e35cfbf0fe3486fa5f7260d1e45885173002d30a28ca019994124255", size = 2439803, upload-time = "2026-10-11T18:32:51.025Z" }, + { url = "https://files.pythonhosted.org/packages/1c/bc/d79d000e5203ebef39af839f2ce77a777fcad6e08ad26af9a2fcd114ffc8/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e722e156d0444ecaefbe640bdb60928752bf2013e2b7a11cdb099aaae19bec", size = 2158402, upload-time = "2026-10-11T18:32:52.714Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/4902cb5fd599422c130bd3124ab31ee8b771199bd56662702eed07d3fec7/pydantic_core-2.50.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:aa8224f10880d9bf1b5993988ba153d42a8b4f3f4f511f93b1f09c93ff613c72", size = 2229755, upload-time = "2026-10-11T18:32:54.126Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f5/c1481f8669f6060d89110c9b1374173fc8767ca276b84f4870bd8acd5e3f/pydantic_core-2.50.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:41bc8237121bd8dc8d888dfd6279fc166ffc88c1f1bf3a8bf00869680533ca4c", size = 2242303, upload-time = "2026-10-11T18:32:55.641Z" }, + { url = "https://files.pythonhosted.org/packages/04/f9/77fc3c7653ba9b6e42049e17e96b25388187d4a7c274ab1cb07f58ac8419/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:45c6266d071c241f2a168d45bf8c54344f0effce35e7e6b73afdec11f3687568", size = 2278345, upload-time = "2026-10-11T18:32:57.38Z" }, + { url = "https://files.pythonhosted.org/packages/95/9b/0579c5d12e7f2b16b27e6782427987341fcce07b0725e02ddb0b74add0c4/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:1deeacb112d14d3f4fcb16b165f7dbaf76c70ba6e82f37ba042bdab51970a0b8", size = 2449947, upload-time = "2026-10-11T18:32:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ac/1b677db91eba54cc5922f4de6edc46ba45c2bd712dfdc0130382d158e214/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c96fd793b73d1b92e65570132505498fe7b21eaef73cdf74e67e5dfba7ac9e4", size = 2476889, upload-time = "2026-10-11T18:33:00.665Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2a/3a9f6624ee3ea9ccba5249dde11418bb4c35780a7a92608f8768bd3fea39/pydantic_core-2.50.1-cp313-cp313-win32.whl", hash = "sha256:06ead20d39ffd6f2f6f2a8f8a6de67ff8bb1b4f14a8a30e058502514ee2ac685", size = 2101118, upload-time = "2026-10-11T18:33:02.329Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1f/323f78ddd9d9938aac420c1abb4e8ba799fc8bdab0acc67c0593b837c979/pydantic_core-2.50.1-cp313-cp313-win_amd64.whl", hash = "sha256:7816e98acc08119dc0f340ab167048ecc54126316330c1f0caf7c6756c88e28f", size = 2170840, upload-time = "2026-10-11T18:33:03.919Z" }, + { url = "https://files.pythonhosted.org/packages/cb/09/8497c52a739ae425c3ac2f7f56414cbc711c67d374346174f40fe2062644/pydantic_core-2.50.1-cp313-cp313-win_arm64.whl", hash = "sha256:c17799a62c142d61b8a3c51752a7cbc87fe2ad4ccfab10e628a77b405075c662", size = 2161633, upload-time = "2026-10-11T18:33:05.518Z" }, + { url = "https://files.pythonhosted.org/packages/49/33/28b96e81677153715e3eafb9f26663a80841e859fde282a380359b0d3fa1/pydantic_core-2.50.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1cf41f1ae3fa155cf167a72689ad044bcc1e3c97e064123677149bdfb5dafc4a", size = 2224428, upload-time = "2026-10-11T18:33:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/94/40/15c06410c9b7b8da5805d27b64e09bd3f900e986728106db2689dbc51513/pydantic_core-2.50.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4df197990c15b5a37c5a277d131d9f2c67de6133f2e5dafd80d9bba4b99f46f9", size = 2023740, upload-time = "2026-10-11T18:33:08.763Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4e/5eb629f6efc2a27e421d789dd4bfacbb5f6d09c80c28b13d1e87d73163d5/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0036473f5583e6a60e50b8b21651511564277a3f05cc5dab8cf579f552cd5f6c", size = 2049435, upload-time = "2026-10-11T18:33:10.366Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8e/f195aebec49ad12318876ac2368c197a7939f5d52f8e156d2238ef8a4588/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:992c3514ec891fa7858099183e4d64e6bd5a5d4ff452fae29df22faa77a006bb", size = 2172975, upload-time = "2026-10-11T18:33:12.368Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f5/7ad9fb83010cd5ea0948409db105676ed779c4e709e2d3d89b9fe2558794/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:739dc730e6be3bd5ec2f4ab5cfc7eb047cc45fc1497b3bafec74ff2ed07df597", size = 2364197, upload-time = "2026-10-11T18:33:14.244Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/bf0aab3e78301d202b82a0322ec968cd703b11fc0623fde9a28708936f62/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fad3a91e51b6d2039c572db04a5a873260b399f6bd62c3552671fa7a4a2899", size = 2444259, upload-time = "2026-10-11T18:33:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/38f6d6564fae57d9b12e5676dfa947e0e7d5c46dbeaa7eb3352261d6d299/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b54c2c90ad348b5e3a85e03e715d572c1fde357ef104cdfe3b03b697a404ea", size = 2159857, upload-time = "2026-10-11T18:33:17.51Z" }, + { url = "https://files.pythonhosted.org/packages/65/a0/fb0a3ca10f139dcf765b2d312d10cf0f65c57c59993229d00ad12b14ceff/pydantic_core-2.50.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:2df1ff41884de2bc4b307bafd7c40a691094fad2ff8e767e5b45a319257bcf4e", size = 2233954, upload-time = "2026-10-11T18:33:19.591Z" }, + { url = "https://files.pythonhosted.org/packages/8c/6a/e63842252702aa4ec6e6b7178ca85e541a77459076592c23ebe2d9840b33/pydantic_core-2.50.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe90228920fd8ff2be62622b6bb8a2b11acd65046d50c6b130614b5879605a20", size = 2243876, upload-time = "2026-10-11T18:33:21.393Z" }, + { url = "https://files.pythonhosted.org/packages/39/25/5991cf8318b37e0dfab47b87541619a1df8501a793cba0d978846cba37a7/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:844b869f118e22a41a091bdcedda8a71bc1b0f62c38d1a0c3211cece47e1d8fc", size = 2277908, upload-time = "2026-10-11T18:33:23.324Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3e/3ee8baaa6cc25a6961c69168cf9ff0f002d56f4e0d541ad6724c18fb61f3/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:2eb75304506894a281d346220a4f7481a1b8729577c5ed2a05395991966a8396", size = 2449285, upload-time = "2026-10-11T18:33:24.942Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/acbec6deac13fe697a80c275a9b6661db62c4d323bac8a47347b1f39c7cd/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6b20a4bffabdad0db2927ac034ae3b8a681b1f7a0182f3e60b479ad2fde21ebb", size = 2479013, upload-time = "2026-10-11T18:33:26.925Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/21a3f237b264389f6219d053ee78fc1b5c2fd402c1b1cb2b6cb8b85f9834/pydantic_core-2.50.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:99ba9bc2b8062ea0c326a990f7f00e6530c23579de66dd246e72c4cafef950a5", size = 1484400, upload-time = "2026-10-11T18:33:28.693Z" }, + { url = "https://files.pythonhosted.org/packages/09/ce/077a6d262d12ef09108377ac0717f029f420d773ace63cafd6143af75568/pydantic_core-2.50.1-cp314-cp314-win32.whl", hash = "sha256:cf356f70551d40374eaffb1aa63f1eb6d2006681cbd7a9faea173ce0f4dd7cd2", size = 2103222, upload-time = "2026-10-11T18:33:30.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/4c/350a2415209c43d670eb71d3c040f332c04a30583d39e311b05c7ac15762/pydantic_core-2.50.1-cp314-cp314-win_amd64.whl", hash = "sha256:d32f3acc081cc3923386d88f422cde8892335e95f034e0104bb4cf9310d9915f", size = 2171586, upload-time = "2026-10-11T18:33:32.139Z" }, + { url = "https://files.pythonhosted.org/packages/bf/92/9bea6ca96580a0902fed366f064f0829e404f41889b34543279a1162b888/pydantic_core-2.50.1-cp314-cp314-win_arm64.whl", hash = "sha256:bed5163e03b98bc1fa2eb05d74c63d9c5c95d8ed6254985481640fbf5e237dea", size = 2155773, upload-time = "2026-10-11T18:33:33.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/8c/f121f073cf32bdd5cba7e6230b1ce0ac845b0cf43e44dd597d95af272db2/pydantic_core-2.50.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9572c1369e9c9da2d64a7b7992c786d90ff295abc93964cfe3125e4290768070", size = 2234017, upload-time = "2026-10-11T18:33:35.588Z" }, + { url = "https://files.pythonhosted.org/packages/a4/69/2bb2bcd6146cffe98d760a46c42ae71efd5151d9b2f9c9bf6619a3b32083/pydantic_core-2.50.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2005207aafe1231315718bf6ed5d064a7300fb4772754af35ee72fc68159492e", size = 2001016, upload-time = "2026-10-11T18:33:37.57Z" }, + { url = "https://files.pythonhosted.org/packages/20/b3/fbf854c7d07ec114260c26e9e2071a4381740f9ae09641dbfcbdf2a18c45/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f6047f62a6c5ae08d0a6afb035667aa2d97c3d20d69762e034c5ea144d92a5", size = 2023015, upload-time = "2026-10-11T18:33:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/65/20/6de55b2f92cdb614b745c6e9ced639fc4fd7e1e77604825a88bdece6fcbd/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ef800dd7d85bcdadf4c3076e4c94e43939493558a3b69a1ea830c706d4617bb", size = 2192543, upload-time = "2026-10-11T18:33:41.381Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d9/19e91c94bd5c405945ce2f15526808aea37e162c253160de8ed7bf70b406/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0135bcdcaa0f23573f286e4cb5e0fd2962700964ed13df085b85f2b97aeab9e", size = 2388704, upload-time = "2026-10-11T18:33:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bc/2e24c8415eae1a25ee5a5484946a917123bad2e9d01689a759236928175a/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b3a6f334c6a2345ca15318ff894502a90012536404b37c844a976c76c846e0b", size = 2464118, upload-time = "2026-10-11T18:33:44.856Z" }, + { url = "https://files.pythonhosted.org/packages/bd/1f/945b8053cb061c64e102bcaf7bfb9ed740c0bd4349f9bb978a7d4ddff4ab/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e01fbbfdb9be777b316a71b6c49efaf4a08b615d0a98d678cda3023f79d019", size = 2150142, upload-time = "2026-10-11T18:33:46.661Z" }, + { url = "https://files.pythonhosted.org/packages/2b/78/96a3e50bf0d64aaae781107eb9335b7529d05a85793ed6e7241c4cd1d931/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a29a061fec0b4e2d714f277e70a3a18125ecff803f2fea6eade2f2e53711d112", size = 2245577, upload-time = "2026-10-11T18:33:48.574Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5d/a6038a0322232758a6ebfa709f4ca14a60bf5b93940cd0b345056a557da4/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5187624823423e1d1b82b1072ac41dc837389e18d3d0572cc19bbee46cd550a", size = 2263728, upload-time = "2026-10-11T18:33:50.527Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4b/76ded3333a457a9344c4f2d63f2d82d0101654b05178fa4ddfe7d3627674/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3e46a9eb0a0901dd6275e6b06ac3a464885ef350ec4121fe486869de8053e4bb", size = 2293630, upload-time = "2026-10-11T18:33:52.362Z" }, + { url = "https://files.pythonhosted.org/packages/a9/00/9eca378335c9c1b72bc779bf6e4c2a82ce784f14ec48a0e87102c9870e05/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:756d669f04e62ec4148ecfe22be6a4484d9b1181a6ef32e205ebfd200540858b", size = 2468048, upload-time = "2026-10-11T18:33:54.118Z" }, + { url = "https://files.pythonhosted.org/packages/35/ca/e3832e9cf93651251de43c8c5c029ed680a3c1a0a1b17ee26c81bed08cbd/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c516cc5367ca3448995d42cb994bf3f4c9002d2a7c22eac9622551269ad1b807", size = 2512159, upload-time = "2026-10-11T18:33:55.99Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f2/773469b5a10a39116a2cb17edaf6d722f017dd8da07161b371465311c542/pydantic_core-2.50.1-cp314-cp314t-win32.whl", hash = "sha256:9d1bed94af6a63835461f3cf7502058eb166c58c4778e11d0f433cfb1bd69e19", size = 2098406, upload-time = "2026-10-11T18:33:58.043Z" }, + { url = "https://files.pythonhosted.org/packages/ee/42/0bb74f8f25204b259b11ab7c12dc7f180893b6bd706118b385147fb6efd5/pydantic_core-2.50.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c8dce1f1e0e5358b682a6ad3fa5e31b31d4560997b8e61417e9217c8d60f8a0c", size = 2148481, upload-time = "2026-10-11T18:33:59.913Z" }, + { url = "https://files.pythonhosted.org/packages/47/0d/d801646c9679a4e630e15cf521d4108b93854b42a6e6e02391bd4c6b1095/pydantic_core-2.50.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ceff0acc940be2715bd6ad17b24c0e5304abf44f6efd0f81ee8499e640f9dc86", size = 2174972, upload-time = "2026-10-11T18:34:01.875Z" }, + { url = "https://files.pythonhosted.org/packages/b2/84/23984b763d8862a02a13d27a44b6e8169428fd85ecfed88f54c29108604d/pydantic_core-2.50.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:8a6791afa2245e6c6b180122d105941644f5bd410bb18623b408808cc41a3102", size = 2224599, upload-time = "2026-10-11T18:34:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/4d/90/a63cf8586abc1d1a3f6d9b18f0224789ebf003851f092ebda3c7c863fd32/pydantic_core-2.50.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:84f34323a61a365b4e9295de6028474754829aaddd59c7bf1a040e7487ef8f3c", size = 2024191, upload-time = "2026-10-11T18:34:05.901Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6a/f34bff9808ffb4907fbf5f5040457d253cacf2da7fce9efbc99ca1b1a44d/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23edad659e8dbd8ca7e4e877fe6c81573abbdf215bd25a68b53e1272f58b80c7", size = 2048252, upload-time = "2026-10-11T18:34:07.757Z" }, + { url = "https://files.pythonhosted.org/packages/b2/54/13f419bf1eb59852003818e25935aaf175687d978f4c4bca70e08fe40a3b/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a5fce22f1e87d181e924e12da7d81cfe031fb3881a5ddf26ad28f141756ca43", size = 2173376, upload-time = "2026-10-11T18:34:09.592Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6c/b5a34d24cd0c81669d8f8339d74e6815abcf2f8fb48ab4b49b84c09be1d5/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c73622ef819328873b53109ee4f77ceb598bffedd02daf916102be3228866b78", size = 2364559, upload-time = "2026-10-11T18:34:11.534Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8d/d64d6216a8df365082665927ff923f183056f9049fee08e9777c9ac05296/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce8c25ca38cc0e3d7753ba180808de2c0c8cb24eae0df64491e40921454e9831", size = 2444697, upload-time = "2026-10-11T18:34:13.535Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0d/1b1149f60a00ea21ba5f70e28acbd40feb4598af414f80f53c921fac07c9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7689580e72a642ab5ec64d5f55b2e33636fa43b4ebe63c0c2c965ef307c7d1aa", size = 2159961, upload-time = "2026-10-11T18:34:15.56Z" }, + { url = "https://files.pythonhosted.org/packages/1e/35/f236549299dcc78e71e945495d7ec67e78d20844d0999c60601685003031/pydantic_core-2.50.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d5c0e32fdbce7f1e8ef4d11f655694bf5f4175c757a9f1dc2be09b8864e5bcf5", size = 2234581, upload-time = "2026-10-11T18:34:17.502Z" }, + { url = "https://files.pythonhosted.org/packages/6a/85/26901a490522b7f75ef9bb9a7afb73e5bb550f0e1cb5b99a0069183e2eb9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40f523349960fa30f3ea51404308ff50f9997a90df639590f47a057c1f32b415", size = 2244090, upload-time = "2026-10-11T18:34:19.402Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2c/481bcfc70ceeb77a778ca6e5b705fe592cb64735c108157f81b7dd9c280e/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:d4193206b6587047437f6f11d7e776df23e1c1e23af2a54d9347275614791e10", size = 2277948, upload-time = "2026-10-11T18:34:21.317Z" }, + { url = "https://files.pythonhosted.org/packages/24/eb/f1e09333faa7ba447cde967310f758430cc7c5e987bdab5228816c70030d/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:84bc765b282a9d5b7fe0348b8648904f25a6a04b2139da52b1dd30c8ac3a2c8f", size = 2449688, upload-time = "2026-10-11T18:34:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b3/036bde636db8f76d92996e81aefc75678ab5cec4a07eea1ad0c72a893fc3/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:ed1e728b39a383c81035b2459cfcb35d99dfb01f7d6ebe3a913bc1cc5b81e459", size = 2479286, upload-time = "2026-10-11T18:34:25.214Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/07f018294ee18d144afeb6df1a47d9e92960f014be45c5ed13519fa5af95/pydantic_core-2.50.1-cp315-cp315-win32.whl", hash = "sha256:bc94f474417604bd383d2cd445d071b07dd55fedceed3ce33407bf1fcc107290", size = 2103683, upload-time = "2026-10-11T18:34:27.42Z" }, + { url = "https://files.pythonhosted.org/packages/5f/98/f9bd7e1f9b6709f155acb9ef826d9c3884fe925f811fd8e55b9b52280bad/pydantic_core-2.50.1-cp315-cp315-win_amd64.whl", hash = "sha256:983a662de2571cb2502fc8ff47b6770b03d025d2eb314c92f77b3f07c74720ed", size = 2171681, upload-time = "2026-10-11T18:34:29.508Z" }, + { url = "https://files.pythonhosted.org/packages/10/87/4bb3e1e7f385c076ab5af4d6dd0571b22042cd8207eb810d5b9fef15ae31/pydantic_core-2.50.1-cp315-cp315-win_arm64.whl", hash = "sha256:94845ff54dc5193f228cab81b2662a04bfbb892e95bdc15edf7399000ce57d54", size = 2156251, upload-time = "2026-10-11T18:34:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/83643225b08f2aef6c8cc4bbe6e3f79c5e139c4364a6e450d0f399d87774/pydantic_core-2.50.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:4a53d13cdfbedbfa87f08b83c1a0a5efcc767d785a4b41934fa9cb672670493a", size = 2234363, upload-time = "2026-10-11T18:34:33.65Z" }, + { url = "https://files.pythonhosted.org/packages/5e/66/127ca649ba2f2462039dc1e694c6c01e00a3689f023a617364d3507e6d97/pydantic_core-2.50.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:efbecf43d321f7b9281441f1f213f7c21c66988b0e06c2730ba13ed47a46bb08", size = 2000972, upload-time = "2026-10-11T18:34:36.037Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e421e0a5d653b1203b090b2e48745976988d7338a647940704b5b9c2b399/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1f08f68dac9f9e83845a8039880aba2ab553eb9b2259c3243a313182c253fe", size = 2022925, upload-time = "2026-10-11T18:34:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/ea5568e2491e1a71100f15ae8c2d01ef52db42a184a2d4e716bc79e5eb8f/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5dfe41f232befddb9c4377f6cfc702b51595e2d78ed082672adf8758d2c4619f", size = 2192867, upload-time = "2026-10-11T18:34:39.921Z" }, + { url = "https://files.pythonhosted.org/packages/ae/5e/3b8c3a35acbe219909ada5defad5d7d9fed845fb2b37bd5ec518f453c119/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adc06d218a1cadfd2ec4628424d7d79ce4eba69c2965e7e7b55106f0da5208c8", size = 2388687, upload-time = "2026-10-11T18:34:42.184Z" }, + { url = "https://files.pythonhosted.org/packages/b6/aa/7889b4e515f91a2e8c0ae6b5081fec0feb30c4398d1434e14793c60f173a/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cf91809d0721ab81592ba67bea7694821679c10b1a2e3c3460082b286c1918a", size = 2464415, upload-time = "2026-10-11T18:34:44.384Z" }, + { url = "https://files.pythonhosted.org/packages/08/78/93449e628eb8a6fdcce3eff9043081179d1bc7ce6f1bff32dc5006f41e00/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23923ab9292c40da026330b1ecf4dc2618c8e86e0422e5d1fbf50d94d64ca4f8", size = 2150823, upload-time = "2026-10-11T18:34:46.392Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f1/72c5bc129fceb0d00f05dc1e67f518c1728de1928c55f81fc13d7690de39/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:f3377c8c2b3ce898423c5e5dd94c7982e30aa7717a7e6ab2470b9de364963709", size = 2245806, upload-time = "2026-10-11T18:34:48.805Z" }, + { url = "https://files.pythonhosted.org/packages/28/2a/922a0e78f3aa6ab837b59f88190233fb8dad546c17995fde9bb3ed3b9b49/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:455a773617b5913bf5c20d0692e5787b119e52c4d40ea644ca31f5758fd31be2", size = 2264160, upload-time = "2026-10-11T18:34:50.876Z" }, + { url = "https://files.pythonhosted.org/packages/52/a8/0f1449e3e1b20941c9372faa18e7b6a092e30cdfa841902473f7989532ac/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:1a9006395dece0e32e704c315eff8a00bede494f6108546cfc5539c89fef4f9a", size = 2294031, upload-time = "2026-10-11T18:34:52.876Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d0/1031f492857de70355fb16524bbb03efce5fd34c93ed4a1ec60be07e0d4b/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d2d82aa62521c55ddfb000ae70f88cdd8de974078f6024e821dfe5addd0c818f", size = 2468423, upload-time = "2026-10-11T18:34:54.995Z" }, + { url = "https://files.pythonhosted.org/packages/46/52/269ffffa645b8e47906395a39cf9db1151ae9fa4bcd7f47b960bc31baf37/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:009634b83993777ddcd69cad0ffcace43dabde692109528e35f0fde91e386a8b", size = 2512598, upload-time = "2026-10-11T18:34:57.149Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/e47e28281f20326ff6f3c31d626f0a83e615d2d94ba41bb0ad6ec237184a/pydantic_core-2.50.1-cp315-cp315t-win32.whl", hash = "sha256:3fde4fdc6487a58d944ca87cf5adc95d5f266e872c19599f5f4c0a8a1b1f9f9f", size = 2098535, upload-time = "2026-10-11T18:34:59.313Z" }, + { url = "https://files.pythonhosted.org/packages/03/ad/759e181e69c1b472c60b2049e5f61d5da1deefdd5ce1df4bb2ebcf771f25/pydantic_core-2.50.1-cp315-cp315t-win_amd64.whl", hash = "sha256:1c8632d4ac04e6f91128fca584b3a8a507d81604c24eeaaad00d4be42765c32b", size = 2148645, upload-time = "2026-10-11T18:35:01.571Z" }, + { url = "https://files.pythonhosted.org/packages/6d/56/8a702c27e5be9f47e5f19d8669227424290e4c024e7c370279cbaf244b4e/pydantic_core-2.50.1-cp315-cp315t-win_arm64.whl", hash = "sha256:c3ede305158e75510be50869b319550ab072008c13d64d4ab1e094fb286b6f44", size = 2175297, upload-time = "2026-10-11T18:35:04.079Z" }, + { url = "https://files.pythonhosted.org/packages/e7/73/a23a327237d9bb985298ce47702e196f995ce850224c12c70886bd66bb5c/pydantic_core-2.50.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:062e891facce5ca296a1c37098e5e466780457f86413894b399f0cf22934f769", size = 2162560, upload-time = "2026-10-11T18:35:06.274Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/33b37b5e82408f402a5e82649a43e7c95cd5dab7beaeef2c35cedbfd18f2/pydantic_core-2.50.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:49c2cbb2397fe4d0987e84606e691af6cb87bc0ee1bd3e7b737f7e10b4c142f9", size = 2075250, upload-time = "2026-10-11T18:35:08.455Z" }, + { url = "https://files.pythonhosted.org/packages/04/62/c7d5b9249466ccab90f3ce4790c2399dbfb8172ae9e74a7753dd295fe65a/pydantic_core-2.50.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e4472072de0137ee0d8e72d6620e85939c271d2f90f6bbb4b15c24638b79f92", size = 2107891, upload-time = "2026-10-11T18:35:10.918Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7e/28d29bc1657195933147f772dee0558d9e0f045bbc11db7f992f40c37bb5/pydantic_core-2.50.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ed4f3cef55164b026fefb41341b7754cc6b624c75dfe7142d2ecceb5ad21c87", size = 2268026, upload-time = "2026-10-11T18:35:13.336Z" }, + { url = "https://files.pythonhosted.org/packages/a5/09/1bcf160f3cd6333e2a76982fae5963e5032de0bcaaba38464223dc266bfe/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:76e2e83fa6ec8cdc972d438dafc2522b3a47bee4ec0ae668b29cfb1977ab5242", size = 2217070, upload-time = "2026-10-11T18:35:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/3d/81/f02de95eec7794dfc7a5bc69a834f0e834b31b3009014ce6de903855f775/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:a51eee75939cf811ac09b278745a6cee7dc873ccfbc8b9af3cc88fe4b7ce25b5", size = 2039027, upload-time = "2026-10-11T18:35:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/4d/16/227746ed3771d9bf2304568b55134849ece34e13b38b1d76de763015c77f/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae28183297fb0d2b8dc46a1f01d51f5e45825fc5afe76a835a6cb7fb34821295", size = 2109009, upload-time = "2026-10-11T18:35:20.218Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b9/7664d1592a0e5a74903f357cd5b28ceb7336f3473256c5a4e8c8432f1881/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88e492e8b9d0312e7dc13667c30222abf284dc3b79b5302b3607b41a5784ce61", size = 2269883, upload-time = "2026-10-11T18:35:22.48Z" }, + { url = "https://files.pythonhosted.org/packages/bd/35/002a378302ff91d2a7f149bc8d22363faf581ba40ac39d0123444315a32d/pydantic_core-2.50.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7456d699b13954e9c0164dcb267250a10ae0dfb03e6e26d6796ab0d46e189c84", size = 2154076, upload-time = "2026-10-11T18:35:24.743Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5c/7a034330082c00f3c9795052bd9b161aaaf6e1951e897ac00a094823ef7e/pydantic_core-2.50.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0d955195bbbe489ad343fcc956eacea9357b79cb22192c66cacdefcbc14b32f", size = 2085850, upload-time = "2026-10-11T18:35:27.009Z" }, + { url = "https://files.pythonhosted.org/packages/57/fa/d5b44a7d3d4eceed6ef8713a0003af56fefde6ec75915513bcdd5d730cd9/pydantic_core-2.50.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2c634642694e6a0dad2ab1d375589fa671fd442edd5caf7d9737b8f6ca22906", size = 2259754, upload-time = "2026-10-11T18:35:29.763Z" }, + { url = "https://files.pythonhosted.org/packages/b6/88/e01dd37301710bd8b3b2daf34ec9e2a6e83a333130831c0d468b0292a48c/pydantic_core-2.50.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6db2fbed51a7991302e8fac498cd67e336246026d0dfa84cf5166ce1412760", size = 2268712, upload-time = "2026-10-11T18:35:32.654Z" }, + { url = "https://files.pythonhosted.org/packages/3e/43/5bb80d4a4d2b2d84612a30205662cc521a20d209c94abc079ea48b4e0df2/pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:79490e33c4c0fcb933bbbcfc3a62184d8803b99f535863dfbb925e1bcb6945ad", size = 2305049, upload-time = "2026-10-11T18:35:35.392Z" }, + { url = "https://files.pythonhosted.org/packages/e8/7b/e3ef76b269cfe4a5bd3bdcf5d7124cf05e5a8909d443b91639307116bdae/pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:48569b0ade9edfbe065cad1d700175546592aebbb42f02adcebcc26e75b896fe", size = 2466001, upload-time = "2026-10-11T18:35:37.791Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7a/ea0947aadc1d9f72d4be00d2e19939611ec17363bcb5e6eb9ec7d8221c34/pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5f3cae32fc46121f787cb2486de9cf95a8bf72aec5cc78f64c606fa1735a6ef5", size = 2481777, upload-time = "2026-10-11T18:35:40.287Z" }, + { url = "https://files.pythonhosted.org/packages/24/f3/d15bc0b1fb0c4f1e07b3326ffb7489f7ae70da2b56a0884d22641b0eb47c/pydantic_core-2.50.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7f476456ac2bb0d937f75191494a09c83a30765fea4f70f3b404942fe25f6cdf", size = 2336787, upload-time = "2026-10-11T18:35:42.558Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/ca/7817b4bf0c4b15c6a6ab8cdf4cb96c268335a8ca2f525de93b6ebe4c7d28/pydantic_settings-2.16.1.tar.gz", hash = "sha256:aff602a1886338c2df3d1e958e2193a8dc33cec75b91f202e83fd98484b4b0da", size = 303436, upload-time = "2026-10-20T11:37:19.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/74/fdf926afe145d8629c4b2ff57f4ea1d64ed7abc4d84d97463b2517a2547e/pydantic_settings-2.16.1-py3-none-any.whl", hash = "sha256:30f5313340da4e8f1b440d1910b92a5bc3ccbb6b4ba70ec2b8395bc921f418f5", size = 80771, upload-time = "2026-10-20T11:37:17.298Z" }, ] [[package]] name = "pygments" version = "2.21.0" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pygments/2.21.0/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pygments/2.21.0/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9" }, + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/ea/5194e52748b0da83d71e082d75496eaec6e58f419f5e184786ded517e6a9/pyjwt-2.15.1.tar.gz", hash = "sha256:4f259e80cdfb6b3fc18a7de51fd1ef9ec79652f25019bae68975ca2468a34df8", size = 121252, upload-time = "2026-09-28T18:40:42.598Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/ca/44de4e75f8aadc457f0634be3b542815078ded46dca30efb960edeecad6e/pyjwt-2.15.1-py3-none-any.whl", hash = "sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193", size = 33860, upload-time = "2026-09-28T18:40:41.429Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, ] [[package]] name = "pytest" version = "9.1.1" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, @@ -106,173 +2150,1065 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pytest/9.1.1/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pytest/9.1.1/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] name = "pytest-asyncio" version = "1.4.0" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pytest-asyncio/1.4.0/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42" } +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pytest-asyncio/1.4.0/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1" }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/26/2fbeedb218a787a5eea551c7532cac4e009f83d689dd2faa0d0353473f86/python_dotenv-1.2.4.tar.gz", hash = "sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0", size = 60824, upload-time = "2026-10-01T05:36:10Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/d1/38f3a3405989a89ac18390803e70c6ad7c7760da4f9b83cbeca0c44a0c72/python_dotenv-1.2.4-py3-none-any.whl", hash = "sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc", size = 23266, upload-time = "2026-10-01T05:36:08.633Z" }, ] [[package]] name = "pyyaml" version = "6.0.3" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f" } -wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/pyyaml/6.0.3/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b" }, +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/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { 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 = "realtime" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/2b/9169d867f4dc0413bb2e493b2e262acd2fd91056aa7614c23a408f79d62a/realtime-2.33.0.tar.gz", hash = "sha256:e4b10077d59876a75070b7e72074945ea78b4a99369771f8b968f4092847c65d", size = 21172, upload-time = "2026-10-15T19:14:19.493Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/d6/4a5c8b6b12104816d9205732c1cb649f47e75367e36c06eba6bdd54e9b77/realtime-2.33.0-py3-none-any.whl", hash = "sha256:da94bb2e6e33859d1aece87656444498a1f7a5ca1c3484a84aa4a23f8940deec", size = 24158, upload-time = "2026-10-15T19:14:18.493Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.9.29" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f2/af1da9d3ceed77bfcdce40427d49ba0be94e4fe84245e3bfef68c10e75b6/regex-2026.9.29.tar.gz", hash = "sha256:8b5fcc4771732191b2b7d1dd68d8f0353f47f8d90b6150f6dce58bf1112442cb", size = 419199, upload-time = "2026-09-29T00:49:58.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/37/404442e7296554792e2de4c0c8f8b95ee491a3c227d6ff90d4cebf2dbde3/regex-2026.9.29-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9916fda742cd4eede63b286f58c06718324265d727ce0856eb1aac86d0d150d6", size = 495402, upload-time = "2026-09-29T00:45:50.322Z" }, + { url = "https://files.pythonhosted.org/packages/72/6d/116db2946888bd60db8e2033739471be6d80aa47c833afd952c9ed40654c/regex-2026.9.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8873c4a11c50b9989168881aeb3f08859f469d809941866aa1feefd8be5431f6", size = 295139, upload-time = "2026-09-29T00:45:51.826Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/aefd12fb5cd62b748aa6ed28725b375c71102853fcbf15fb6481f806a2ba/regex-2026.9.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d9fe8091b2e89d470df68a9331111ed008ae8aae6bf1e8e1fba4086a495c84e", size = 292130, upload-time = "2026-09-29T00:45:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/98/41fe60ae6ccf3e166fe7bc73495aa15e9a1a7fe3a1f9f0f95a3d64a2df0f/regex-2026.9.29-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb00027a09a8f9f08028b40dce4c933cf73e4833240ed356583fdc9cfa721566", size = 793080, upload-time = "2026-09-29T00:45:54.331Z" }, + { url = "https://files.pythonhosted.org/packages/45/0d/944b13e8286ecba61a3baeca28b375550821a279800bdb18fac41ade6637/regex-2026.9.29-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14e953ff3607c92d7675bf79c4d4509ef6782aa8c08509f179f9b3d6d0679e86", size = 865241, upload-time = "2026-09-29T00:45:56.065Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b4/e1c32fdee0462f1373c52a73325cafbde653fac0b0cc1e9badac74154f4a/regex-2026.9.29-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0476e5bcbe6e1ba3d1c4cc7bbb1c3ba78e3b979b5c8a88d0a6a8cdd4992b8c84", size = 905496, upload-time = "2026-09-29T00:45:57.491Z" }, + { url = "https://files.pythonhosted.org/packages/ec/99/eae371ca63f7ea1f1eac025021527adb57ce3a4b67ff608b896fd31ea447/regex-2026.9.29-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fb41211d2333eb930a51e0546a65999761cf1f572a4da56ef9b8a62966c06f2", size = 797643, upload-time = "2026-09-29T00:45:58.746Z" }, + { url = "https://files.pythonhosted.org/packages/dc/26/9e4f158459e37e41633c96f49766a465f801c5daba9507260609ed76d783/regex-2026.9.29-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:edf06545875f3efa31560d94121e95c7fd70d98b1dfedc0157097d79b13b52ea", size = 797591, upload-time = "2026-09-29T00:46:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/86/60/5e1c5c6e85132e757ae77bd7693f1809046edb2c8853658c9200f4c8593c/regex-2026.9.29-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6398d5145689503412cc1748895242598d8846b8967b851133b20dc2ed1e21e8", size = 774874, upload-time = "2026-09-29T00:46:01.491Z" }, + { url = "https://files.pythonhosted.org/packages/e5/13/301b394f32a8db86ae3280533c9186229595fed766376f18e87959f92f96/regex-2026.9.29-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:45010bcfe66df41522d56c9b6114e87ecc597a08970ff6a2ced24415c141ae5f", size = 780168, upload-time = "2026-09-29T00:46:02.817Z" }, + { url = "https://files.pythonhosted.org/packages/f6/27/0c647127db32760b2dfc4c38da9ca509a3c4a8d3d93a586121a10fef2701/regex-2026.9.29-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5758353650079898dc1b2b0e95aa51fa23a30d020e06f62c430dd08ee56cdd8", size = 856604, upload-time = "2026-09-29T00:46:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/4f/18/cac15fb2829c33218ececf76333b66869a96e4f973480ec32494921750f0/regex-2026.9.29-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f7121a8914ed13fcfe2099f895341bfb789f004d4c5a0bdece8fa667da10849", size = 760602, upload-time = "2026-09-29T00:46:05.794Z" }, + { url = "https://files.pythonhosted.org/packages/b8/55/967676e4089a25d5f54b9d62522f9fe09b87677d1db58f207888d4ccfbe3/regex-2026.9.29-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b9d74e4eee9ddb64c2e92d5d61472c59c21684c059eb7b68767be9628e977859", size = 846830, upload-time = "2026-09-29T00:46:07.258Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/bb4165d61860ed443da5fb0fafe629c1ee9202526394eead7925bdee2be4/regex-2026.9.29-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:143533cc4b6fbc5b95aca0a5b8d541088d374831593def000ec89322c220221d", size = 785445, upload-time = "2026-09-29T00:46:08.89Z" }, + { url = "https://files.pythonhosted.org/packages/86/a6/a5cfc2560e063f8e6871373d3b719f573d7e7fcaa09db43e7c8aaeb51bd5/regex-2026.9.29-cp310-cp310-win32.whl", hash = "sha256:b84f186a7f0536fe4ff9a9fa12d06d007b9b71d4b5352ddcc41f59ad6522a312", size = 268950, upload-time = "2026-09-29T00:46:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f6/b127312ebc8092357f435c9e4594fd8cf50abb85f8b4b16ab8d262abab2a/regex-2026.9.29-cp310-cp310-win_amd64.whl", hash = "sha256:23ae6fdad9e63e54038f5ef78aba2933faca61e24d432786589e737bc5522ebb", size = 280629, upload-time = "2026-09-29T00:46:11.638Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e4/ab36f3c26f2374d16d525dd5a7fb4997f3c6e6bb7c6722821b84f9ea35b1/regex-2026.9.29-cp310-cp310-win_arm64.whl", hash = "sha256:c0094897d7d01f184b2d7fe8c56c66d64efe01b31f4b7d34205b391387df1111", size = 279407, upload-time = "2026-09-29T00:46:13.248Z" }, + { url = "https://files.pythonhosted.org/packages/e8/6b/6dea87689c3a06a6e79d254bf824e6f3e3d724b5ba027c6112559aa6cd2c/regex-2026.9.29-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6abb75ab16bc3281714a5b99548a2225db70dba1f995f6d7f7419b76eb5a8fbe", size = 495413, upload-time = "2026-09-29T00:46:14.51Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a5/0c791a0e83ad1013d262c13247c4c77e0f4a8d05bdc167df96aba6681c0d/regex-2026.9.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b7b893976e7fe42053da64f2aa27239c24252fd2ec6df471e1be197c0addc3b1", size = 295145, upload-time = "2026-09-29T00:46:16.292Z" }, + { url = "https://files.pythonhosted.org/packages/b1/07/9bf3607d8d13a12e436ab9d63f9791e10706827d535695b23964ad79fd79/regex-2026.9.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:066d0e3dbfdd739bce2bf8c2a41dd16f73e3d8adc2eb06dd803a36a307f56075", size = 292123, upload-time = "2026-09-29T00:46:17.646Z" }, + { url = "https://files.pythonhosted.org/packages/64/6b/32c2e6fc617e1d3f247e250fea31a9a35b1265bd32f585968aa13b9999b9/regex-2026.9.29-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7020ed44df30b3aa492c00ee3b52d0548c1f30c2c6c5bb13ae897680900d3413", size = 799537, upload-time = "2026-09-29T00:46:18.976Z" }, + { url = "https://files.pythonhosted.org/packages/bf/72/f041177f3c7a4606f7c81a95fe7eea03e2a0c4e8bff9e439a01432cbc9f2/regex-2026.9.29-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4613d7d9dda60fcba95f846cc6f808017f1843f392cf9daad14a6534493d71", size = 873255, upload-time = "2026-09-29T00:46:20.684Z" }, + { url = "https://files.pythonhosted.org/packages/d0/4e/a78948e11dd715e0e46716c2e0f3404b3fe6a44e2a2e9abdc7d965cab2b3/regex-2026.9.29-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec37990e3d6121f29ecfb594bd8f1bf009e9f7926daba2e50e3b27d3892a783", size = 914570, upload-time = "2026-09-29T00:46:22.599Z" }, + { url = "https://files.pythonhosted.org/packages/8a/70/aa08d1d2b294894b365e5f8ba5380fe3f8546acdb81f10639dfd74209c37/regex-2026.9.29-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:612b709381c0355b70d89cdb51b7f670591ed5cbbc0e3b5337488019dc667b65", size = 804978, upload-time = "2026-09-29T00:46:23.981Z" }, + { url = "https://files.pythonhosted.org/packages/21/32/1b03534c4715aca3b564416d28d518083ed4dab3bc913267600d2256140d/regex-2026.9.29-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a760da040b47767b4b873adfb7c3b691e9ba2fc60f113f9d0b88f1a62f323e85", size = 779297, upload-time = "2026-09-29T00:46:25.318Z" }, + { url = "https://files.pythonhosted.org/packages/76/a7/378f6f558d9e4444af315a307c5953565a511d1e3666f1bb7bdc82012b6b/regex-2026.9.29-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ee178ca31c94621294bf9b8b676a92a2e6bba8af0529591753719e57edb621", size = 786961, upload-time = "2026-09-29T00:46:26.963Z" }, + { url = "https://files.pythonhosted.org/packages/59/13/79f0b1846f5f342f92ddbd4b27b18bcb86da96d902c1a0be26520bde98d7/regex-2026.9.29-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5eeb8edc6110d9194a4d0d54610f64c37a31c605b5dbb7e407fc6ec7fa34a4a1", size = 863234, upload-time = "2026-09-29T00:46:28.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/19/05af70dec9f2eed6ba34e08d2dcc6a48e7ae5e307659d5fe4201a5d7bbee/regex-2026.9.29-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ccb64d887a9db1cd76dbc0f92051a1a478a2a67e7f56c62d915cb881d7734704", size = 766487, upload-time = "2026-09-29T00:46:29.941Z" }, + { url = "https://files.pythonhosted.org/packages/01/e1/9c7486d4afe8fdd1fe0ad60139f8aa91427381f409af6a29b609d8fdcb3a/regex-2026.9.29-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9e4482589065c8ecd761cff522dcd85f2d39e62f551e37e025d1c7d54772def3", size = 854792, upload-time = "2026-09-29T00:46:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/26/c7/49d008ff5f741d9a9799d7315556f3a12b983ff0fcd2cdfb62904bedafbf/regex-2026.9.29-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d60030baaa7bfbb02d650c126cdcddcb6e33dbff14d819434c8fa2fdcaeeeba5", size = 793050, upload-time = "2026-09-29T00:46:32.775Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a1/46ba549e65562ca04608b24179b8a7bb6f146ae0e7c6d7f5e70f3339c8ba/regex-2026.9.29-cp311-cp311-win32.whl", hash = "sha256:18ae8eed4526e35bdb754d61562b90bf5c00a67fdcf3cc1380dd59597486631b", size = 268940, upload-time = "2026-09-29T00:46:34.179Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4a/aab232183c70fdcf77bcf0c51819da02ec522e393e6a0bf00bcf2142e21f/regex-2026.9.29-cp311-cp311-win_amd64.whl", hash = "sha256:1043aedf5917caa861bcb25a9c11460049656bdf0017a90a309fa8f255467725", size = 280641, upload-time = "2026-09-29T00:46:35.484Z" }, + { url = "https://files.pythonhosted.org/packages/33/b1/7c05954af0f51de376df2ba97f7f78a8b79334c7e5b3d2d9f2aead1f4d3d/regex-2026.9.29-cp311-cp311-win_arm64.whl", hash = "sha256:352cf115a810b357caa35193ab656ecf5ef41056855e82f292c99e8514f8d954", size = 279411, upload-time = "2026-09-29T00:46:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/84/48/3fdcde9a0baa84d7d25571223265d6e434e114763b438601d54a8028bf3e/regex-2026.9.29-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc79d36d0618752265f0d575915bdc5c5130ecb9c9f6b3bcefeae32e4bdfafcf", size = 497903, upload-time = "2026-09-29T00:46:38.938Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1c/4ee3e97c76f53940488dfe7a7e18705e78daac8cd7fb161d246b9e328449/regex-2026.9.29-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3a21a9509d0ee88e7a70e1ad228cd2f0e0fd1e187458db132e8a8d18c97daf9d", size = 296416, upload-time = "2026-09-29T00:46:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/37/14/f3f0ba083d2094392d5eabf56db5ea6ba469fd6e927afd187042054ea68a/regex-2026.9.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f57dc6b8fef170f105d2cf5cdce254f47b137d7755086cf7050f47e16582abba", size = 293633, upload-time = "2026-09-29T00:46:41.959Z" }, + { url = "https://files.pythonhosted.org/packages/c9/72/67e7a8ce17f1aea49df215564048efb49cc8c2b31a0e0fc30f36838f8516/regex-2026.9.29-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f93bc1c3486ef3747e07c9d7c1d0a147b8fbaab975f80e348aed6f71309dfaca", size = 805885, upload-time = "2026-09-29T00:46:43.373Z" }, + { url = "https://files.pythonhosted.org/packages/f6/78/25436bcfd4d2260b4b4090094d55d7ab53ec8a1ab4865a0b8bcb33c7d5c0/regex-2026.9.29-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e1d3a4cb7993b708f0ada8d0c84590efd853f169e7147d2202c9da503180242", size = 878344, upload-time = "2026-09-29T00:46:45.328Z" }, + { url = "https://files.pythonhosted.org/packages/97/e6/a09ec3a23ae41d6179880e67f0aace9284b2d95f2d7b326eff203f8eec5e/regex-2026.9.29-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dabee8f4935e731fb46b2a3091bdda0d3d94b3bbfb907d2b4f12eefce4009619", size = 919181, upload-time = "2026-09-29T00:46:47.041Z" }, + { url = "https://files.pythonhosted.org/packages/26/83/d2fbd2e4e3afb1167daa825187d196f313cbaa1a4768f311fb041bb0e3d2/regex-2026.9.29-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39ab5894d971f9ac68baa6eca5c50387db579cfcacf36ae8df3feceb1815e6d0", size = 807783, upload-time = "2026-09-29T00:46:48.894Z" }, + { url = "https://files.pythonhosted.org/packages/46/0b/eb429a7016610d44fc89a597163f8c9127505f0d7dc724dc9effbb6a3ac0/regex-2026.9.29-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c1a9a6651197fbed6f0212591418b9def774fc3f8324f78d1bf0e6a63e5f8aa1", size = 783465, upload-time = "2026-09-29T00:46:50.64Z" }, + { url = "https://files.pythonhosted.org/packages/1b/07/58a3c0153c7476898430f6a7cf3d9062a1d17fbea4f43399ecaf411c7b4c/regex-2026.9.29-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87fb80cbe3557e27e7b28b995c2b2eedf689b8886f941ab93e0e288f0976518a", size = 793519, upload-time = "2026-09-29T00:46:52.396Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/161b94d39164520e21a7befe0245569bf7fda4c7cf1fc4e2df2b5def49da/regex-2026.9.29-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3c5c2ef13797466aa64170cbb66ad98a32351dd4127694cea7199f80f213750d", size = 869293, upload-time = "2026-09-29T00:46:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/8f/07/3b02ed829aa2decdc1955d222bd1e2f99d1c8bb4873bbb9a66b2f0a36bff/regex-2026.9.29-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:59b49507f47479e299a9e1bc41b5cb83a7afda0540625f1dbae886615978acbf", size = 770239, upload-time = "2026-09-29T00:46:56.106Z" }, + { url = "https://files.pythonhosted.org/packages/42/5b/ba61f6fe062eb8562e742367d177bb75370434138ef6c9d2a27114f8d613/regex-2026.9.29-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0dd8af32e9f7b56b7f95cc1fd79b23054c3bdc172392ae560acc24d57b7ffe71", size = 861973, upload-time = "2026-09-29T00:46:57.665Z" }, + { url = "https://files.pythonhosted.org/packages/cc/27/767259b20e8a842948990f5e99138d6c077248fd42f8b5468b1d9ca4b814/regex-2026.9.29-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db5e82ba15c142425b8406690032df89e39cca4a2e8afbbb9a3d84edc2373ac3", size = 796470, upload-time = "2026-09-29T00:46:59.236Z" }, + { url = "https://files.pythonhosted.org/packages/a0/05/2566c4ba849b68a8ab81a6bf428fa79d20aae7ddee83979103c0381df254/regex-2026.9.29-cp312-cp312-win32.whl", hash = "sha256:d0c3082bf79bcd6a614d55916590ad4b8f93200e10b97f463ea5d9d07c9b5f23", size = 269327, upload-time = "2026-09-29T00:47:01.135Z" }, + { url = "https://files.pythonhosted.org/packages/93/19/489bc8db91196381c935752df01ba3f607140daece33b78d88573f028e64/regex-2026.9.29-cp312-cp312-win_amd64.whl", hash = "sha256:fdd88ed5e20b1bcdd234421e454962c971aa44b653bdb7f1ea9ef683e90fb649", size = 280334, upload-time = "2026-09-29T00:47:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/fb88ba779d0e5e7d4b0ec1aceeb13845948a2cb876bd572a2d1dfdba090b/regex-2026.9.29-cp312-cp312-win_arm64.whl", hash = "sha256:4fe97894d1b306c919b4e50def1e6f6c522f4d03a7283811f4d108f1ce5d3ac2", size = 279614, upload-time = "2026-09-29T00:47:06.541Z" }, + { url = "https://files.pythonhosted.org/packages/79/d5/6080f7d1a6e7e36aa720f806ac93c035ba39c209ae6cc510e8ef4c0279c6/regex-2026.9.29-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1a0d5117230dd46b399a30a38afa44f79c99f3168988fdc4f425c3f928b39df", size = 497622, upload-time = "2026-09-29T00:47:08.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/c87fc7a2e21a42f9d57489db32951c37eef56d153840459a80d464f0321d/regex-2026.9.29-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f0fe9834e5aeccaf19a0d8feb296d66a24be1a7c9922002f842a682cd5abb787", size = 296281, upload-time = "2026-09-29T00:47:09.764Z" }, + { url = "https://files.pythonhosted.org/packages/11/9e/aa0f4cde3bc4688c1d58b0cd8415edd708339bc0bc401a195b0b1e8c8f0c/regex-2026.9.29-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c90fcf7804ea0a54b896ce0f2b9565350220b8d4890fd0db461a476a4c687963", size = 293472, upload-time = "2026-09-29T00:47:11.723Z" }, + { url = "https://files.pythonhosted.org/packages/90/d4/e835c487850ed922a8d6074f953b888c8ea99775c76b9ed5f8a4d72eab92/regex-2026.9.29-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e11edba5bc344a32b029a7af9d4b3173982dd79eeafa0b9dbd787364414b0509", size = 805966, upload-time = "2026-09-29T00:47:13.235Z" }, + { url = "https://files.pythonhosted.org/packages/2c/57/ba8809847fbae8d2cbc71367c6ded510a7ec88bf52493c65efc1acf4effb/regex-2026.9.29-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bb90e7177944b6684738c1fc36aabd2dd00d1de3be7dbe09f91e196f1bc0dc81", size = 878346, upload-time = "2026-09-29T00:47:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/e3da19fc3cc15ef67ab67e121e87887c3bccfdb683a7a9ec557c460ca5b7/regex-2026.9.29-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d06fcdecc10fc7954d7c8f27a03c96055fe525274dc84a7b0dbdc3d6b9e03dab", size = 919250, upload-time = "2026-09-29T00:47:16.622Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8e/c1ed81f55f992f6aa0b699a592a50c1ce9e6d44ff1aee2c14c0537dcef9c/regex-2026.9.29-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d49c18f1ea294cf4adde2e5ac256e98c82ea9d708462ce4bf799dffa7cfe8a2c", size = 807902, upload-time = "2026-09-29T00:47:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bc/5a6886eb470e41040e21e05b75024a18b6ebfe7ea400b72094a60f949101/regex-2026.9.29-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e778bfccd63075167709136afbc251c1f683758d5bf49c803c60ac3f894ce6b", size = 783514, upload-time = "2026-09-29T00:47:19.921Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/6d951d453b023c6edb880f1ba474291b53b8ce1cc438b96a9db6d791d991/regex-2026.9.29-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:686ac5350fceae63830bb98805fcb8039325bf4c06d9f6f048ff65229d5bffa5", size = 793466, upload-time = "2026-09-29T00:47:21.552Z" }, + { url = "https://files.pythonhosted.org/packages/99/b9/d5a41adc08360f5eee0dc4846c578f002366947211fc8af5a69a64ee7b9f/regex-2026.9.29-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:26ec4ccce55aa533fbd603d08911b01101a8fcfec987845ac3ae2c7087b2bde3", size = 869464, upload-time = "2026-09-29T00:47:23.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/d76c9d91f5d798e2e9e67f6f85ec4ae35445ac425f7454797311cecb80ca/regex-2026.9.29-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a655d34b2a6943af32401f3d94f72e9d731f6ad16285815550bf2b4ee69d420a", size = 770278, upload-time = "2026-09-29T00:47:25.193Z" }, + { url = "https://files.pythonhosted.org/packages/24/00/aeebdb540c620a0f7317f6d6fad80a47729ecf0599a24b5c34ec155351f5/regex-2026.9.29-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0c992c19cd45058a4b92f68f139c93db168b48fb1f322c9a7cd620806afb6b51", size = 861949, upload-time = "2026-09-29T00:47:27.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/62/d0314bcedfd3586197e4596931fa220260eb2385bf53184e5b9ae67db24b/regex-2026.9.29-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ebb8912f565b8cdbbf27debfe00df04202c20e2f651b9e32767930c5eace3621", size = 796602, upload-time = "2026-09-29T00:47:29.233Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c7/d5a8c13a613facb03e0fb55c1ebaaf7bb35d8e2c1abe8bef8dca809fc1d9/regex-2026.9.29-cp313-cp313-win32.whl", hash = "sha256:4d7d93613b01b0199961330e49cfc52d479b3d5776c56c691db31130c0a07d91", size = 269306, upload-time = "2026-09-29T00:47:31.14Z" }, + { url = "https://files.pythonhosted.org/packages/80/a7/bf93a3a6afa5f7bc16b7afb94ae581b01cae620b8ad56bd8f9572a985959/regex-2026.9.29-cp313-cp313-win_amd64.whl", hash = "sha256:61956f074ecd123f55adca68ee3eab46e6a07ad3f8e64e6db95dfacb444f55c4", size = 280307, upload-time = "2026-09-29T00:47:32.709Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7d/388274e53605a86297f433a08102a7bbdcf9379d47683d307ccaefd88e2c/regex-2026.9.29-cp313-cp313-win_arm64.whl", hash = "sha256:bfc71e6d970419c1309b3640305298643e2a734cad3f7cfb6d2ddee4175ab53d", size = 279599, upload-time = "2026-09-29T00:47:34.674Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/d9dc6f02f569625faf67a4daec926cd5023472dcd69bb44286dccd5a5ab3/regex-2026.9.29-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:957bb708e8057ab1649ba566456429d691ec9b90d1c9ad1af1ba7ffbbeaf05f2", size = 497598, upload-time = "2026-09-29T00:47:36.541Z" }, + { url = "https://files.pythonhosted.org/packages/9c/83/9b693a3fd1451381e812031a8961ec5b3b8f0c8cc6871f14c5223642804d/regex-2026.9.29-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c9b602fae1e00b7c035d661ce85575365719192a7b46784bd71cf64c68053aa0", size = 296250, upload-time = "2026-09-29T00:47:38.233Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5f/52bc2abc3fef040cd9de76ab29c918d6a717a454ae2b9dd7938b0c95656d/regex-2026.9.29-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0166844493626c5015c6088ee15c9ca2fd060ca15b7641d1657da6a58432ae33", size = 293518, upload-time = "2026-09-29T00:47:39.957Z" }, + { url = "https://files.pythonhosted.org/packages/dc/fc/cf50671215ee0057046980b4571ef8646a005819bb67f0957e779ed107a5/regex-2026.9.29-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b97a38fb4c732b6832db6bf108963adbcd82ef1268ba2025dce390f45af75efa", size = 806037, upload-time = "2026-09-29T00:47:41.676Z" }, + { url = "https://files.pythonhosted.org/packages/14/4b/dddef8fc15c63e4347cc9efb138d0cd306f30e6c98acbcc81a8f780083b9/regex-2026.9.29-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a540abfab208e1b7ef2df231c40ef3b6cbb30a0aad6204e9b6a81c10a6794628", size = 878881, upload-time = "2026-09-29T00:47:43.755Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/38daabed32d28f7e58a06e9344ce00dc67952e9996bc578ed6a29fe1240e/regex-2026.9.29-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ddfa987262763c3c22a8367d2a49c244b018a74c3a8e3ab1a864119ad45c5633", size = 918684, upload-time = "2026-09-29T00:47:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4d/041d9458a645fee4fce4d642a89d27271a3cfcd91095104f6dde44da70bf/regex-2026.9.29-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f7f7aa47b229f2b39a2ae2596d2ad5625d77b5eb9856fac2dab3eb506cdd0a0", size = 807176, upload-time = "2026-09-29T00:47:47.372Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/4383eed7aa5aef67616cb1b3f3ad06b7c624c4e6cced48630cd5ce133d85/regex-2026.9.29-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d9b77b25b4f395f92de6099ab08e8ae2bc7e51dfe157f22900902243a5cc90c7", size = 784315, upload-time = "2026-09-29T00:47:49.518Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/0086ad31cebb183c637d3198547075aa493afde308e1ff61fccccb29ba6e/regex-2026.9.29-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34b6925af9853bf461950e6508910f179fd6e9b1a7ec8548e069606b7e51a26b", size = 793748, upload-time = "2026-09-29T00:47:51.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/f9005cba3f629a859573fc5d1224ea4e1f97919ec8581d018e03a351a604/regex-2026.9.29-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:addd736a0547d553283adaf4e05d7104e7f2c7b0b092e9b4d28756825f14531f", size = 870302, upload-time = "2026-09-29T00:47:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/01/4f/e1a3e46bb5315a4e18b01a990e7a28e2a16595609d50c442baf2815a3c65/regex-2026.9.29-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fe3fa1dd453ed5c7f5ea23a26218329790ed7197a99b90e94330e313959a7f52", size = 770299, upload-time = "2026-09-29T00:47:55.606Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fe/f303b4acfda44e1ff1379368748c1ef2dad04a6a8e9c0ecbc970b19d97ca/regex-2026.9.29-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0cc63b5e47c12a48d90c7e9d7de6a035dd14f62868aaedbb4e0ff8ba2b8bfe7b", size = 861570, upload-time = "2026-09-29T00:47:57.617Z" }, + { url = "https://files.pythonhosted.org/packages/60/b6/b4f7e99249f596017c60ccad5faf9310fc8e3e59bb2244940a90a1b0bdff/regex-2026.9.29-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:724184b4aafed865e4f13ca313fdcb43024300c028ec67319cfa16847d84685e", size = 795967, upload-time = "2026-09-29T00:47:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d3/fc865a4638d9f6762192b6bab5b7aa1f33a90e9e99578c2e111e2a63c8c3/regex-2026.9.29-cp314-cp314-win32.whl", hash = "sha256:c6c8fabf1dafc1f1ddcbb67896d3f93efb092e8c4b6322d7389b944e76a484e5", size = 274758, upload-time = "2026-09-29T00:48:01.8Z" }, + { url = "https://files.pythonhosted.org/packages/31/e2/c2b466924ccbeb874862968ca638051b15a8fd29d994a0e99004a5cbf78e/regex-2026.9.29-cp314-cp314-win_amd64.whl", hash = "sha256:1c2a0026062abcc321a53db4a185ceba0b59a66b5d37b0808917a88b55a5257f", size = 283817, upload-time = "2026-09-29T00:48:03.614Z" }, + { url = "https://files.pythonhosted.org/packages/c6/42/ea0f8dbaa924fa75c6338935eaee2f44dab369b27f02db1e03d74344b049/regex-2026.9.29-cp314-cp314-win_arm64.whl", hash = "sha256:121a76a0985db80ceae9e171c337f8c927868e37d01b54e3ce87bc87f9c6a208", size = 283663, upload-time = "2026-09-29T00:48:05.624Z" }, + { url = "https://files.pythonhosted.org/packages/44/48/d58e5081119f5c223bbb37d2340acde3d069e1df8e8cd166c37502eee4da/regex-2026.9.29-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e31f72490b7c12f7790e1e25c3afffd20503ee1bfb43461d7838b871ff244b19", size = 501393, upload-time = "2026-09-29T00:48:07.833Z" }, + { url = "https://files.pythonhosted.org/packages/72/3c/c49945287d4f9efee7d41f98072f8ad880efb8f430595a612fbdea996a4e/regex-2026.9.29-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:80ea96f5c1a30bf09007d48466521d9c294bebe197c708c3359096e3e3691632", size = 298237, upload-time = "2026-09-29T00:48:09.684Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1f/688cb61c3d4cf7bcc1ed444b5cc49399eba3e51c469ae285cf87fea3022e/regex-2026.9.29-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:554bffadcbcb6d5f4e5fb10a61cc52084b9a63d1dab5f10bcd2c4343972e8e2c", size = 295936, upload-time = "2026-09-29T00:48:11.454Z" }, + { url = "https://files.pythonhosted.org/packages/26/a3/de43ac6b877b7d09c19a3a426b1bd5acdd209eaaf68f406466f80439ccf6/regex-2026.9.29-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:864e9b87ac33c3fb9fb4ad48166d4fdb579c351d5c77deb0d34bccb36a775cd9", size = 816905, upload-time = "2026-09-29T00:48:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/62/14/9940763201c51d537786304984c67d0fc3d2ed18837ffb6f09a869f6b6c9/regex-2026.9.29-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:044265d77d94f5e3cb2fd72c76723807c429cb8c533e9d4672d0334a6f14f588", size = 881527, upload-time = "2026-09-29T00:48:15.313Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e1/c842d8df0b23245ebf202f8ab9c39fd48e2db39959454ec39a41c8c72082/regex-2026.9.29-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2089fe39c406784d90101c726755ffa1497bb74638fd434300d2b88006186de8", size = 923115, upload-time = "2026-09-29T00:48:17.328Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c1/98622479e3c354a446a75232e522d747d2b3df23092dcd8a5309380a2020/regex-2026.9.29-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0def9fb6abac55492d6d51cddb7225d07d6f279e774e0adc08569a54a5fc8d46", size = 820674, upload-time = "2026-09-29T00:48:19.32Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/5808c95f9c79ed27b5eedaafc3df6239ec56a49f2e23ea8f831b18427c82/regex-2026.9.29-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:888d60953908dcf761aa320c3e390ab8556efbdb551ace63921de90f6ae0848d", size = 793306, upload-time = "2026-09-29T00:48:21.615Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d3/021ca2638671ad20603bcd9b4d5bfa35d2610cd216a043ea7f0b44ea39f6/regex-2026.9.29-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed511a0708e2297e1d6431e7fb217e3402791e491e02da800658ace4973df1bb", size = 803103, upload-time = "2026-09-29T00:48:23.871Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2d/755c6d13ef9c657378013676c391c7a402166b3f419a464a3e058dcbe533/regex-2026.9.29-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e1172147d28d8fbcf8cb8d26c41506169f5ad8fe9ec969cb116835a19d4d8eca", size = 872175, upload-time = "2026-09-29T00:48:26.255Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fc/e1cab183b9dafe8597f58c1c766da9bf96204d3b2f232bcf3eeb75ff7b6c/regex-2026.9.29-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:92f05c9c42bde5785dc48770bc2194d9f7442544156f951e19cd31b096cec562", size = 777362, upload-time = "2026-09-29T00:48:28.389Z" }, + { url = "https://files.pythonhosted.org/packages/06/7c/e10ea17fba31fb4a1f9d13ed53a2d2a9066a2aea58d7557e263f6d99e7b0/regex-2026.9.29-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f37964e4a5e993d2fd45147741e9dff7f34a2d8c00ab94c4ea0514a4677f959e", size = 865606, upload-time = "2026-09-29T00:48:30.4Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6e/69824d9aee1fd41c54ea7264654a47c8d9d84d8a228e11c2bcf4c201ed81/regex-2026.9.29-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:951733b1bbdb71e377cec567b409f1a7881b47cfcad84121aa74cb575fa425ea", size = 807945, upload-time = "2026-09-29T00:48:32.375Z" }, + { url = "https://files.pythonhosted.org/packages/89/22/857050a86e21ce60193e02a8ef662521f2e263a645c8b1b905fc136b61a7/regex-2026.9.29-cp314-cp314t-win32.whl", hash = "sha256:65b408d8fcb273e3499e7ef2ce796810da1becd208c7fb4373692a242d79d461", size = 276758, upload-time = "2026-09-29T00:48:34.72Z" }, + { url = "https://files.pythonhosted.org/packages/4d/96/56808fe029553d7d4c703414f2a527faad2ea2bfa9ca094a2e7f8762b530/regex-2026.9.29-cp314-cp314t-win_amd64.whl", hash = "sha256:bf48516e35cf848390ea68850aba53e7c333720d2945b4d2c25b69fc5171723f", size = 286527, upload-time = "2026-09-29T00:48:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/074e2cfb3d8101a6a764aba5f7c5d1e21de087483e35bdc0c4ce2eb60364/regex-2026.9.29-cp314-cp314t-win_arm64.whl", hash = "sha256:9173db3be74a35cb6731701094b98120f7ee4876a287882a59cdea1fa7da342f", size = 285954, upload-time = "2026-09-29T00:48:38.901Z" }, + { url = "https://files.pythonhosted.org/packages/a7/dc/d84990386c9dfdf8c377f00f371b241fdc9a2c8aea0e3d66941b2e51be0b/regex-2026.9.29-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:c3589f40749acce747510bf5d589d54e376cb0930ea58b35effac97e5312b0c1", size = 497836, upload-time = "2026-09-29T00:48:40.858Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ab/a569ebde875fa12ff8c6c9a30e07503620f195e4be4d54c3d3ee8eecc283/regex-2026.9.29-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:32ab11df9677ca80bcbb5fe4eb1da9109a5019239a054836efc6fa1c64e683cf", size = 296244, upload-time = "2026-09-29T00:48:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3e/7d548e82a108e7c8b2d5246650e397a2f8db599f9b2e975466939c5b4e70/regex-2026.9.29-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:7c03031610e3e6ed1768a2b7a8fc84637c1257b50c5eacaf094c6e17a84fc563", size = 293748, upload-time = "2026-09-29T00:48:44.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/34/a8e19a52f452bbb07b32a2bef70dcdf90c2737049749f74cc12d7486fb4f/regex-2026.9.29-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:42e82e578c904445d4c8a35b8f28052cf567593215fa5db06266fbc6f77aaa2e", size = 807840, upload-time = "2026-09-29T00:48:46.948Z" }, + { url = "https://files.pythonhosted.org/packages/88/7b/11fbd4640b3bb82b72822a63c20ade4013d562d291703a9debeedc24e682/regex-2026.9.29-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0b65c72739f981377c9c22e0c5c3cd7f42da7bd8a3c9209330fac772c7d893ed", size = 879330, upload-time = "2026-09-29T00:48:49.168Z" }, + { url = "https://files.pythonhosted.org/packages/f3/55/de58c74f1f4e31586d83eb39c56872d686c4e0d0966d151884c833b94ced/regex-2026.9.29-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4408b2b27a95ca8cc48b7411945753773353b5c93b307754781086c99d3a576f", size = 919251, upload-time = "2026-09-29T00:48:51.322Z" }, + { url = "https://files.pythonhosted.org/packages/81/42/a8c480f6dd5ac59fa28ddae79afd9d7ac7e596fdb61813adc65bb6e674b8/regex-2026.9.29-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a714befaacbd10092ffe4cea0d3c5f008fb9efe9bc322c715bcdfdee414b9a3d", size = 808808, upload-time = "2026-09-29T00:48:53.529Z" }, + { url = "https://files.pythonhosted.org/packages/68/60/0bc0d1ec8b37ad64be6fa30e035251f11de9667a0fac9e82ee74517d81be/regex-2026.9.29-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33026515aebc0e70d1c89978e53e8d695d35d9e472f8d5b34465ba3c74028650", size = 789907, upload-time = "2026-09-29T00:48:56.036Z" }, + { url = "https://files.pythonhosted.org/packages/da/84/116a3ef19b3acfe81077f0bf2cbc7714a5e94bc8935b7243ab61cb0f1c3c/regex-2026.9.29-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:31b003f9a070335e2a8233ee9b14a3ca8e6d792012ae011f741bf0aaf11744c5", size = 795770, upload-time = "2026-09-29T00:48:58.284Z" }, + { url = "https://files.pythonhosted.org/packages/96/ba/e38c3f203e7e7e18c957d48e6cb6dbf96c11e95a44efa4a480522afc5d6d/regex-2026.9.29-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:c03c6eb6ece86dfdcbb34799efaa339b093132e1aceed491ba5e08fe06cdf699", size = 870671, upload-time = "2026-09-29T00:49:00.506Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0f/9ee0b0cb76c55f63684bd7fff554978e8773b4fc86e2bcb2d50772dc1086/regex-2026.9.29-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:a5300757f8a68f5b6cc33f57338d72a0e3589c5cc9ad5f8504ea06f028be582a", size = 778631, upload-time = "2026-09-29T00:49:02.984Z" }, + { url = "https://files.pythonhosted.org/packages/b6/19/e6e3eeb226af5872c4958002f6edef4e4f40ea4cc5f5665023f2019eb045/regex-2026.9.29-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:80c7cadd3fd2bfde5df8aa0787e315812cad0c313a753095d02f4c2b6c01677b", size = 862187, upload-time = "2026-09-29T00:49:05.264Z" }, + { url = "https://files.pythonhosted.org/packages/5b/62/823c102e106bb2711d6b7dfe5981552fe4467b2969c46a20c5c383cf498c/regex-2026.9.29-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3f1e6cb402a89457582cd696f982559217d13484a193202c394015297968c86d", size = 798423, upload-time = "2026-09-29T00:49:07.644Z" }, + { url = "https://files.pythonhosted.org/packages/37/e0/e927776258fa70b2f6feffc3be584ffc85ba4c1e20a320f0aee9a632fc7d/regex-2026.9.29-cp315-cp315-win32.whl", hash = "sha256:a64b85a4760337cfefdb27d42da6ed8b58e8cde3f2d57b6ef43e76ef6ea9ef47", size = 274757, upload-time = "2026-09-29T00:49:10.513Z" }, + { url = "https://files.pythonhosted.org/packages/77/04/358de85d1860238e1b4fa98fc2c80c990124a25d2e14739e28cc02c25562/regex-2026.9.29-cp315-cp315-win_amd64.whl", hash = "sha256:b3e445b66c80b4eb4234e855ce94d9adc183eedbd632816228d89930b91b2c5b", size = 283824, upload-time = "2026-09-29T00:49:12.849Z" }, + { url = "https://files.pythonhosted.org/packages/92/d3/d5c5b264784a5ab2b0f8cf620c1eeb4dbf3440d306761905e7d99345bef5/regex-2026.9.29-cp315-cp315-win_arm64.whl", hash = "sha256:8f39588af4731c8923c26810eb3b33f76f17633985e40f59c3cd45a33805a895", size = 283664, upload-time = "2026-09-29T00:49:15.331Z" }, + { url = "https://files.pythonhosted.org/packages/02/dc/f63ec2c201445ce1150fe780f5c56f16a10124d9a9da3a93161dbb0d8892/regex-2026.9.29-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:fb99cc9d45f48895d9d67f6a0b8a57f08d39c174d9f25ad97a313e0470267b1c", size = 501549, upload-time = "2026-09-29T00:49:17.705Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/d2a698dc6bfc11fbce03f1cb0249c13284e93b79ed11f893edf6fac431c9/regex-2026.9.29-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:720537c7ea6f80dc61913184edb0ce2497a306b39ef19f28505b322553d52bdb", size = 298187, upload-time = "2026-09-29T00:49:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/85/b7/88dcdb38cd3935d4ee9e9ce9b8e56cb3b3518d1f020acfa7dd62ad289bf8/regex-2026.9.29-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0fd2c901cc307a745ad4bc87f20060d7a0825a3371d1e93488af22e7a387f78f", size = 296157, upload-time = "2026-09-29T00:49:22.342Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/ba6c01dde33a69fc294b38b43f6677baaa5735a6248f39708031a738158a/regex-2026.9.29-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b11b589e00095ec69cf79841a76360f9b079e95b0368a25b5ebb951ab0c157ff", size = 818468, upload-time = "2026-09-29T00:49:24.612Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f1/2586693e3a2d6b1247852593d37a6c17b42a92ee44f7cdcb9a0c1494e64a/regex-2026.9.29-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d7cab119d0df0b9413f106b4d7fc34f2872d3574ed3806fb48959c830b1537da", size = 882825, upload-time = "2026-09-29T00:49:26.996Z" }, + { url = "https://files.pythonhosted.org/packages/30/51/084f3e7bdcd0e9c33665c938cf5d134dc3548cbb4a75f0197ec7bfd754b1/regex-2026.9.29-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b89efc38431793d28b7cd91227e2f952ad7c48df19132b17f43a5fec3c14143b", size = 923314, upload-time = "2026-09-29T00:49:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f1/066c6fc23b7dc229789c21c880b5ba5ad689fb95fed12e078266f55a1f9b/regex-2026.9.29-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80a5ea3b4fd9d6a5b9a44f7976a9acaaab35aa3c1f6b29e5bd857dfabaded223", size = 822222, upload-time = "2026-09-29T00:49:32.404Z" }, + { url = "https://files.pythonhosted.org/packages/0a/56/592cd46fdb8f2f8682a1d7fd1310e4d0bcb93fbd0e6bbe4141ac28240227/regex-2026.9.29-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:19959129885356df0e97556856f77eb2888380dac18bed075a7c05c5128c618d", size = 794882, upload-time = "2026-09-29T00:49:35.076Z" }, + { url = "https://files.pythonhosted.org/packages/ee/4d/d65384bb071c864b01aa8314e3a6a687845ebd57588390976edc960c218b/regex-2026.9.29-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:6a1a824fbed817e0a891103886b68f063b1e83cc51bc97192a90a60195a9291f", size = 805887, upload-time = "2026-09-29T00:49:37.395Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/358de0d8f40d5178e4f7e7e121cfd5b961c812b77a055d11f5079e3f8fd7/regex-2026.9.29-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1ba8c6a416569ce0d37e83e28a254a61dc99a419084dfb6476cea02d997f74fa", size = 872901, upload-time = "2026-09-29T00:49:39.927Z" }, + { url = "https://files.pythonhosted.org/packages/00/06/6bfded72d043240c6b52bbb5e16f639d81affbf7484b4fe2ec45f3d4afc9/regex-2026.9.29-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:446654b29bfaa30500d80947eda42cef1449dc8a87f4e3cf061cc8485d3a1f0b", size = 782970, upload-time = "2026-09-29T00:49:42.581Z" }, + { url = "https://files.pythonhosted.org/packages/5a/20/9f418a50baa78b3ed8308fcb0cc49e472dd000b7ef935a7295af202ea744/regex-2026.9.29-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:bf3c49863c23a1ad6da9c30351aed6cff8d5ddbeb63c5c8420ae54e98c7d0138", size = 865441, upload-time = "2026-09-29T00:49:45.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/29/817c7eacdeaf8463123e949bd394c39ad024eea1ec38ddf5ad141da2f3bd/regex-2026.9.29-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:01000ddf0e3ffef97f2413ceb514f6313040106b6d18a03ee00a4fe35c1eb1db", size = 809431, upload-time = "2026-09-29T00:49:47.878Z" }, + { url = "https://files.pythonhosted.org/packages/63/0b/83aab3b5b739947f744135a7a3a446e25433ebc92b05e01aae197ccbfdda/regex-2026.9.29-cp315-cp315t-win32.whl", hash = "sha256:c4e38dd8f39c43a91d2410ad2b85610701b0979342c3df1d69eaf8e838c757d8", size = 276964, upload-time = "2026-09-29T00:49:50.524Z" }, + { url = "https://files.pythonhosted.org/packages/72/f2/6314b5fc68789b5dcc38885bc6e3d6986b34fb3372b7231088ee5cecaa05/regex-2026.9.29-cp315-cp315t-win_amd64.whl", hash = "sha256:e2c89e9b762c57f59d5e99ee8b20202adb892e35f8d3485741340999ca55058e", size = 286487, upload-time = "2026-09-29T00:49:53.224Z" }, + { url = "https://files.pythonhosted.org/packages/56/bc/97b2245c8c7b2dd01f2db74f2bea003cd33c15009b4996a2447f46b5325c/regex-2026.9.29-cp315-cp315t-win_arm64.whl", hash = "sha256:e8c65ef3862a8ad6e86492b6ed9327805dd66904c012bd3649dc67d822ed6c34", size = 285955, upload-time = "2026-09-29T00:49:55.655Z" }, +] + +[[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 = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/42/68/3bd46b8a5e01d3c2ebdf9c5e9497912e3fe0cde02bac21a7130ca866e403/rpds_py-2026.9.1.tar.gz", hash = "sha256:4793ef7f78268b124b73fa933440f01d258bbae01de9fa53e9080c9ab0425a12", size = 63948, upload-time = "2026-10-04T16:32:36.469Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/6d/ddb8dfcf41892ba9c672d27988592c038d30db61708230313831b6780a1b/rpds_py-2026.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2711d29b653b3bce48a63d18b9c6b53274669e6d6c4094dddeb4d9a0e45128b2", size = 344947, upload-time = "2026-10-04T16:28:55.207Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b2/632c3d034961149eff606e3e357547620a9ab37b849ae5815f67838753fd/rpds_py-2026.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3231c4c0e521dafa5be0c9f114ee2c2ad46650836f2d72caa86801950c3e7044", size = 346770, upload-time = "2026-10-04T16:28:56.884Z" }, + { url = "https://files.pythonhosted.org/packages/6d/45/4df346410bb873d03ec60c3b891d1fceb8cabde1971d29844bbb625cc296/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e01b3c878c8641913e688edd1b3f08658c6783d29cf6b826bd3c0d1ae7a1ffaa", size = 377795, upload-time = "2026-10-04T16:28:58.193Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b8/a70f302207e4f29f873c01a986fcac5def49b9c8224328cef1ad7da6b7a9/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e524c7874ac72884d28e16dd5b8d839fd09e0fe76b020d3fbca23212a7b8c52", size = 383137, upload-time = "2026-10-04T16:28:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/13/e3/3aa28a48a01169d0e20728bf0e5ebbea3ae2774285b40853ae49b3ef332a/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761fdae6728ceb99ab182fad2f0cc1e262f610834dc891aea1d1a2a2e634776f", size = 496789, upload-time = "2026-10-04T16:29:01.011Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a7/414e8d088602d66ad6f47c65bf07fe5090992a8574cdd4571c1a83b647ed/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b723eb406dec5bc9ec516c73ab9c3239a3284e017f7eb89ee2b3258bd504fb7", size = 395929, upload-time = "2026-10-04T16:29:02.465Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5c/aac3b960fd55d37b49f77c0eebbe043eaf36762ebd48e7b46951e59c1c05/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136a1c3fe4402b7008bc81cb62ee538481795b61a7e83df88dff3b3f02b726ff", size = 375426, upload-time = "2026-10-04T16:29:03.944Z" }, + { url = "https://files.pythonhosted.org/packages/d3/80/77987a41c40be7202a78d9aed4a650bfb05792237cfd3b9e22638d990fe5/rpds_py-2026.9.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:839dde845559254f34885267c6878f60d61d5205180226d976fe488d45fa128e", size = 381497, upload-time = "2026-10-04T16:29:05.308Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d0/640b02eb1ecd71a913c1178b6235287b31006266bca9a26862e80e497b54/rpds_py-2026.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56cd8b3f77d7b6812f533b662186a1f28316931166ddc00fb893b1b0db7e9888", size = 405740, upload-time = "2026-10-04T16:29:06.578Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8c/d034195d45d215625a23f2ae85dbbdb86a49a93a51272061c97c80b9d316/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:074a4d198bc34d9a8ea425114fc3ded6d11ec01f6a314a8db67454a5152d8834", size = 555850, upload-time = "2026-10-04T16:29:08.115Z" }, + { url = "https://files.pythonhosted.org/packages/de/f8/532f1addfc86207bc3cf23749638bb0d457bce891595012ed8a08d0543b8/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6beb738155fe8ab8091afdfa5a3226b21c2b1593f1e50ebb90eb25b44dbc0391", size = 618661, upload-time = "2026-10-04T16:29:09.554Z" }, + { url = "https://files.pythonhosted.org/packages/50/5b/b0f2c9833a36a5b1f6ac5cb0e635fabacf713266f46ceefba2102da0bf51/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57492a550a1d88d29d003247e5f78dd8cf04a701fac0e4c8db8745a6d2504e0a", size = 583530, upload-time = "2026-10-04T16:29:11.072Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/464ed4b98d3c8c6a334f3b5e24d3886de04b05658a95fc40a37e0e6e4775/rpds_py-2026.9.1-cp311-cp311-win32.whl", hash = "sha256:d95a354e02393eada6d7351184671aced9d4cce109dabf927cb7aa99624352a1", size = 208125, upload-time = "2026-10-04T16:29:12.408Z" }, + { url = "https://files.pythonhosted.org/packages/df/c9/384a9b62f8cd89c8e3e609b8c9052c140e8c01d807261e0517c904b8bf29/rpds_py-2026.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:ce4d4f52e2a4324396caddbd45a97d8d7be5f42edd25d2355282a9c34f9b2f7f", size = 226045, upload-time = "2026-10-04T16:29:13.751Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/0a691f0dcb2301fa9c736346da2d0d4991778373283301c5c12b8f952f3d/rpds_py-2026.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:fdcd198979b4ecffcc1beba366a7fbcf4eb41243691a82fe52ceb0b902f09c12", size = 222636, upload-time = "2026-10-04T16:29:14.989Z" }, + { url = "https://files.pythonhosted.org/packages/5d/34/a828586ea3329fbb50895b50e9cf98ca3d924a9f41a0b26d443bff1b3794/rpds_py-2026.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:50906f5aea24b5a865cbd0a589698288631d9f3a54c3a937c83aefa95a0d14af", size = 346822, upload-time = "2026-10-04T16:29:16.258Z" }, + { url = "https://files.pythonhosted.org/packages/90/81/ac6a0d064982251856ce009c9e1dd51a34110b3c055aa7d1aad18b4899a3/rpds_py-2026.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e21c1429e205828ea886a2293a4a2c8e01f4c25d9893ca330e97a6cf73f52e7b", size = 341380, upload-time = "2026-10-04T16:29:17.706Z" }, + { url = "https://files.pythonhosted.org/packages/42/ff/bf7d54f362748fd6a49277b9d6531c394074110fedf50ad791ec59133fbb/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693b2728bbcc48d09a981a356954b0c47c53ff25b545856f28a889ea619f69a", size = 373053, upload-time = "2026-10-04T16:29:19.063Z" }, + { url = "https://files.pythonhosted.org/packages/60/de/74b0ccdbbd28687b9b5fdb34c1cabed88352182facaced9d6f5486b8b9ed/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8601470267d938bcb7f3ab1a336100af51a4fd5b6ed030ef52461bb3ef5e7e07", size = 377742, upload-time = "2026-10-04T16:29:20.543Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6d/b979775a3057b2a5c26d75ecbff60a20a24ef081db6dd76836fca3f20247/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3890a6aa36e6baa53d5258a2a25d3ef8b37ad165a6ab27a892d7c3e3a432cd69", size = 487289, upload-time = "2026-10-04T16:29:21.958Z" }, + { url = "https://files.pythonhosted.org/packages/1d/5d/7c34734ce3ece943d9d6ee0122a74a21bbc75b47acb5b7053c83f6efb1ed/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b5b393eda5ea42cca1c1a6665f2a4882b4fd5d1777e41ce0545a107fb008c9d", size = 390710, upload-time = "2026-10-04T16:29:23.378Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6d/b26eb1e75395925b3a142ed351cbed2ec8212f5c9d937aee3df32c701baa/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:addeda51556dac7c1a2f14cda62db8b621cd12afba3091d03a96c72932387eab", size = 371261, upload-time = "2026-10-04T16:29:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/7c/98/b2fdfe10301a9e27af21e634337fbba7baac0bc17fe44619a17c26bba2cb/rpds_py-2026.9.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d9edf30457d74eebfd76b045535e36f1cd89062566a128a0db2145ca042d787e", size = 376075, upload-time = "2026-10-04T16:29:26.748Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/c4b8d954e49c3c69cf8063c0c9e0919c99f3d2cecc46311606a1cbe835dc/rpds_py-2026.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:815d26356930846a40c7bc1366e7b1b0320ab8a063e66c11298a208bed0fd237", size = 399358, upload-time = "2026-10-04T16:29:28.282Z" }, + { url = "https://files.pythonhosted.org/packages/b3/59/9559a7293c97dff0cbb293efffbd36644103883ecc27741af8ef90c84ca8/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b5a6f40f0a1486b4b36c888123afc67acdbd9f33235927acf5ff295429a0ba3", size = 550267, upload-time = "2026-10-04T16:29:29.851Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/6dc60e49511de4c8b00e74f9c1d43aed4d27f712cdb1ade92f2c199cbf64/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5b8b0753718d258fd454283fbd57e14545d3b40583fa672e27cb4f987626bcc", size = 614018, upload-time = "2026-10-04T16:29:31.54Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bc/92a4ecf27301b886232a413360f6b348a81d55ffef654f2ebda5970ebc3d/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:46d80bc76b51a6c24f9944368c28d38b8bcbcea1da4f2f8d3ebc31a67e8c6ec6", size = 578509, upload-time = "2026-10-04T16:29:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/08/53/efb97f2e6589b7ab8394385a73870e0b2ab2a195281c117b21fd6d7d4855/rpds_py-2026.9.1-cp312-cp312-win32.whl", hash = "sha256:befc2d6a953e563f8a7bfd87a42c22ebf8a3e980dcb7b6a4d17b70b0e914e8a3", size = 205401, upload-time = "2026-10-04T16:29:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/1c/84/1700cc748d0eaa747486e4e82882d2575224e6e9a3148df583058850c629/rpds_py-2026.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ce8943f79c2210f7abcc28e86367b03b28d95027fd01c46d2472373ae70c86f", size = 222923, upload-time = "2026-10-04T16:29:35.709Z" }, + { url = "https://files.pythonhosted.org/packages/c3/89/0302215373c2f8b4408b0fdfee955368cc378a98ff59508d47a7e0dc2dbf/rpds_py-2026.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:501909f2e4a1e2dee528ef766fe3c469060ebc17e54a8383d404ba07a81a6f02", size = 218415, upload-time = "2026-10-04T16:29:37.263Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/ee88fd9e756ff93fb6b1182a47ec09504a242620e33ce1d20679efefe841/rpds_py-2026.9.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a36b70596407634ca82d4b989a3729074a008537a0522e4c8046a67c729103e9", size = 346229, upload-time = "2026-10-04T16:29:38.82Z" }, + { url = "https://files.pythonhosted.org/packages/57/71/a097d6552f837500fc36e6b23d09cfb9890c3cc47531f9ca64e149799615/rpds_py-2026.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eba5d173f7d5708b22a93815017a4611873ed54db9f268077c0dd1ed99cfc858", size = 340514, upload-time = "2026-10-04T16:29:40.405Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b7/497e85768bf4e0d8ddbaa096a4cac31d1509251dee2728a8490aa367e0b5/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:457866b85daf5034296666168b84a69e0b2e89dc4f1af102b46f6448a60b9063", size = 372611, upload-time = "2026-10-04T16:29:41.778Z" }, + { url = "https://files.pythonhosted.org/packages/52/4b/74ab4108916250b6e198e0d3af05bc6835eb046315f22f7a0ceb49667c5a/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a3a52a3ba86436ab3aef510fbe21512abc2ddd1993005dfe50514bd2284ef025", size = 377712, upload-time = "2026-10-04T16:29:43.242Z" }, + { url = "https://files.pythonhosted.org/packages/0c/8e/067e77d9d7b3cc793c9d909b7e97e7aadbbb1fb094093b6876902cc96d38/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7841166b7fa64c9c56404617ae4341448847482d45933b13135d26c130519e5", size = 488106, upload-time = "2026-10-04T16:29:44.692Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b4/c5aae6c2dde269bf955f6b7d35065c655a57e47750d9668052ad67e74dda/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:926bdd3e3b5998ddf70cc64bc8cf57209571f9044542913afb673799fec77dd0", size = 390795, upload-time = "2026-10-04T16:29:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/a0/36/76fab39973ee11e7f9f357c55138197bb01c86f6502cb76487e3b4f42db0/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7868b85224291c6cb6759f9b5adb9745f486d226f62b16a614dd5a2a5ab2b35b", size = 370794, upload-time = "2026-10-04T16:29:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/cd2a80e6d7b871937a60e935c5d507aa390d143f4ff3636f640b9733d5df/rpds_py-2026.9.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3cd182d7291d29b92c521a0069d9c01ba6193628a9a105531d11b40a6d731a33", size = 375673, upload-time = "2026-10-04T16:29:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/7464a9953724e55a3b3206062fa0ffdeaa519584c6aabf65d3956d94f131/rpds_py-2026.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6ea1cda8d8c688278430e4268a42f5e5da3bdd74578dfadc0820c3f1766ce83", size = 398758, upload-time = "2026-10-04T16:29:50.601Z" }, + { url = "https://files.pythonhosted.org/packages/c0/86/1534b436700fd49ff411063b7c4d7e938adfabf90895b6cf1622d5a7d1f6/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5943980471829f6de242a20b109de3111ba6b77e3af0ffc587028ac854b05e6c", size = 550370, upload-time = "2026-10-04T16:29:52.002Z" }, + { url = "https://files.pythonhosted.org/packages/57/1c/e1fa82a8a01e3c5820f3ba98a8b2673f642128eb368fa88871b01dd2c909/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:76d3af9732d2dab69f28179b40ba2d87e2f1d5824b4a694780aa787d685e8f36", size = 613106, upload-time = "2026-10-04T16:29:53.655Z" }, + { url = "https://files.pythonhosted.org/packages/29/55/b20b8c4c3dde8755bfcd5b08492a02d0cd2e26929aedf6199ca2a377d42a/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:78326f4cb4427a56ba4996c0762b63be45f06b85f086526420d2b3a66e40f84d", size = 578235, upload-time = "2026-10-04T16:29:55.157Z" }, + { url = "https://files.pythonhosted.org/packages/55/42/df3f7bbc3f7ab37a8a9db8d6c2ff2c985422899f1f7926afbf7ec3c0b8b4/rpds_py-2026.9.1-cp313-cp313-win32.whl", hash = "sha256:172e47169583f46ce118cbec68e6795d0da0f4606b488b6434f8276bca0a058c", size = 205293, upload-time = "2026-10-04T16:29:56.669Z" }, + { url = "https://files.pythonhosted.org/packages/31/9c/ba5a9569d719bfdd6ce863df4133ac6a1658cf1b07cc3534c31db729fbbc/rpds_py-2026.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:3e93b2cd69a9830be33e03945cd7cda940a0a8bfcfbff41d6144f0cb0d3d8bd9", size = 222409, upload-time = "2026-10-04T16:29:58.049Z" }, + { url = "https://files.pythonhosted.org/packages/35/72/f28ca566f6c23c35bbf7445f65eb0364577b25a026305995e24f78b83d94/rpds_py-2026.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:d151e148117294133bf8af7eeace085e7e87432db15ab6adf640330298a47f6f", size = 217681, upload-time = "2026-10-04T16:29:59.449Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f2/67b94be1532767803415c1c5a1fd88ea487643d74a673cec1ba140af77bb/rpds_py-2026.9.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c9d1aca01f49170fdcf5c92761b1fafe97f554b721ca4570c5949fff778f0d4b", size = 347178, upload-time = "2026-10-04T16:30:00.865Z" }, + { url = "https://files.pythonhosted.org/packages/04/37/b751de2b59b0197a1d92a5dd491de88e8a5e928c2e6562581974f1e85263/rpds_py-2026.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3f0e9ac28fc067d4d34b88ae43c48e9489455c97fee9633d851f7eeed5a05d35", size = 341878, upload-time = "2026-10-04T16:30:02.564Z" }, + { url = "https://files.pythonhosted.org/packages/72/e2/5873bc4643c250db9e05d48dc0c93763d4aa68bc3b81164cb1af3b45b284/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07deecbfce94c78473018bc7d10b337cc651d12df87a1eb2cb3e4024bc9c33d0", size = 373963, upload-time = "2026-10-04T16:30:04.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/03/5acf7632158247f3f6386ff0af3a1ee48167d575037e8d0920594b76d92b/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:821b2755db9194409254012f429c56643416fb96ef9be090be82ec8826b7f477", size = 377975, upload-time = "2026-10-04T16:30:05.555Z" }, + { url = "https://files.pythonhosted.org/packages/e6/00/63fda451b8bffa5808fc8bb311ee7c073b340974b09f273c7b2a145d3d62/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c91c210ae7645626c608400e3519b4a642f837cce09ca830db3beb2e9f274d4", size = 490347, upload-time = "2026-10-04T16:30:07.156Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/c093ffd070ba0fb02f76c565d06fecc65ad6e4afdbae78f7031076d3cdac/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54ac2158a6f96cfbabff0b2eedaf94b90c5ec7ca8317fcadc61e1c2b2e0ff6ef", size = 392962, upload-time = "2026-10-04T16:30:08.77Z" }, + { url = "https://files.pythonhosted.org/packages/22/9d/d08a1128ab199b2f0cf25bfeb0639bd05119fff4b7c47bec24ef9a8ec23f/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac2f5dbafd585dfe31f86a23ebf0d3ba480a9d49ebc87947267b5608d4ea0cd", size = 371623, upload-time = "2026-10-04T16:30:10.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/c7/4758ddcbb75609414bbccfcb11d612436f9b3ee821bd2f33f0f1604ee648/rpds_py-2026.9.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:8aa5dda18d39b6143eb24809d158f9252c88f402749b6f1b62a506cc7d96cc35", size = 376997, upload-time = "2026-10-04T16:30:12.124Z" }, + { url = "https://files.pythonhosted.org/packages/87/e4/947bd7f608ff60faf46dc9d389c3dffd0e3d767d78a0be19978448ef0ce7/rpds_py-2026.9.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c90e7fa02e8f5de0d10c17595c568ada48c5302e749462c0ea1a4c362111a86", size = 400169, upload-time = "2026-10-04T16:30:13.804Z" }, + { url = "https://files.pythonhosted.org/packages/4b/35/fe93e020a0543b5670472c18d7e6af3197c08da571240ce1965c84f85c0f/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6d198bad4e49dd6732fbd636e2fc5c082f45c8cad0b4acb756b00c82c76072e", size = 551615, upload-time = "2026-10-04T16:30:15.332Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4f/5d2a0136bb03b2a56a39dc6ff92d58a6e3e53a2e17079238b86228882f16/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:96beca19ec79de272e8668585380ff9092c47077c1d7a1e098e00bbd921f4785", size = 614655, upload-time = "2026-10-04T16:30:16.92Z" }, + { url = "https://files.pythonhosted.org/packages/09/1c/3f1025aaf70d9bf7272cc41f8b64ee76b48bf01726248138430e16f23b38/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5cf77eb04f20b720be95265a3e00eb2a14814074255cc27069c551b2db53118", size = 579602, upload-time = "2026-10-04T16:30:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/53/0d/5c72e6204f76610608706da32b6b7e11ef7e10317558a7bb15a122e008dc/rpds_py-2026.9.1-cp314-cp314-win32.whl", hash = "sha256:a03d57b86d2a51d0a66c92177e2be154ad015f357791d306e714569999cdb4cc", size = 206315, upload-time = "2026-10-04T16:30:20.05Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0b/489d48abbcc7d70cf3fbf662d9d22abf1f4650761c0a9ae05260800800d3/rpds_py-2026.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:837c6b305e26fe0f75b15c92cf3b2ba29e0ae19dc40b1c557b026cb426347d0c", size = 222852, upload-time = "2026-10-04T16:30:21.604Z" }, + { url = "https://files.pythonhosted.org/packages/91/16/bbb05a7e6a10cf79ba639be7f799d770ee15f64175cc61d081b218dd402a/rpds_py-2026.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:fce4b85234a0cbad67bf8e6e1201ee815d172c9aebad75f25645bc4d834f8e31", size = 218709, upload-time = "2026-10-04T16:30:23.036Z" }, + { url = "https://files.pythonhosted.org/packages/22/ac/ac507a0a4ec478ca470440a09583db4be5259ba7670aeba0620822f1e57a/rpds_py-2026.9.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a72c11530d71abfb66c8d7696a2f86c43e63fca8b948f1a784ac490f4ec688e", size = 349596, upload-time = "2026-10-04T16:30:24.558Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f2/817a46b658d5070f477f722c298ee9a24525b0e4017347964146ef5fdd0e/rpds_py-2026.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:068c37bba854ec2fe42f7365c640af11dd9895890ccbf2df5070d0c059bd7f96", size = 343114, upload-time = "2026-10-04T16:30:26.048Z" }, + { url = "https://files.pythonhosted.org/packages/6c/42/6ade976b13ac1b4cb3bf2eb603f1be2fe74df19a29988d4c2b386be59d6f/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fca4eb6df565e2a928f1c7dad92d27db8f9df0f449e76423ed5d7e713ed445", size = 374197, upload-time = "2026-10-04T16:30:27.699Z" }, + { url = "https://files.pythonhosted.org/packages/d9/70/77cdf1d3f1a07faabe936016ae623aec7981f73108a8fe7a203ed2e21998/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c933c6678c6f116ff8af47a4c6db0868b8ace74af0343016c0ef00f00272ea69", size = 378025, upload-time = "2026-10-04T16:30:29.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6b/18a44a3beaa9b7931acb04af7bd9539836477c630a794452d4826d6185d4/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:028ad274ea951dac64491b5d1e65712a4aeabfdbdb9fccf797b57bd899b0c495", size = 491969, upload-time = "2026-10-04T16:30:30.995Z" }, + { url = "https://files.pythonhosted.org/packages/73/27/fb39cfd6bddaf741b024f813890374578ff8ac1f1adc473659c048b03b05/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d0a99cf9de0b17a3943388e9294a59becf75e7c43421f387bd3c7a9901f7c", size = 393905, upload-time = "2026-10-04T16:30:32.628Z" }, + { url = "https://files.pythonhosted.org/packages/ed/71/0fa7bb77b57af0d710273964180d11b503f62b8a5c358eb2d8c3f62feff6/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0da298fb372dc192610a4b9ecbc68a0cd8b675bbbd1fc519d01b41cfd658333e", size = 374569, upload-time = "2026-10-04T16:30:34.257Z" }, + { url = "https://files.pythonhosted.org/packages/54/22/f41cfac269af3b449513ef1bc3d7f32fde52abbbd2d01c7e76b47743acd9/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eb61be926bb81567c1f48bdc8aa22b9855048dc2efd53871f9f7e6e9a5632346", size = 377482, upload-time = "2026-10-04T16:30:35.997Z" }, + { url = "https://files.pythonhosted.org/packages/b4/fc/312b49006e7f8f9ca5f96647577b8aa6f3df30519c46bd448f5c425af0b2/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42e75466f83cd43f6026c81eab74246efb2bdadafb307b85700632d06c68f299", size = 400040, upload-time = "2026-10-04T16:30:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a6/18cca7a878dc7fa95165a83343fd4d7b65643fd22e54e47340a451121d5c/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:617f59cde379b4f648a09797b7f683d04b90a46344cddab85639da5aff0f5531", size = 551849, upload-time = "2026-10-04T16:30:39.443Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6d/1f5685e20f39604691bdc3c05aaa6b8bd2f954e9e996477adf2376768e33/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3edae8c5ddfdb6985d49ae9d150516e5076888879022f91a26c2de9276ce0bdb", size = 614842, upload-time = "2026-10-04T16:30:41.231Z" }, + { url = "https://files.pythonhosted.org/packages/c6/25/98652109fd9f7e10268dd4571aa52b81987001b806f37ef1a18260de714a/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0f045bb053c9057720d72c56dffe30dffdc05997b2897a827b9325f0ab6623fa", size = 582606, upload-time = "2026-10-04T16:30:43.345Z" }, + { url = "https://files.pythonhosted.org/packages/19/03/11ca09099bab5f53373a80a334c424ec917c13d050760a59499d2af5171e/rpds_py-2026.9.1-cp314-cp314t-win32.whl", hash = "sha256:bf35d0568abda97233239ce32896d3ad53fccc537832c104e30c94aa5fb93569", size = 203341, upload-time = "2026-10-04T16:30:44.954Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8a/88909e3ffd9f47f5b58211473875d8c3c09079f0058c46fb72c55a702a26/rpds_py-2026.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:1e8d4d79d828299bf44a55db22a9388ab967b49d17132c88eab0f4360b48da8e", size = 222843, upload-time = "2026-10-04T16:30:46.486Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b7/a662f367d4896dd0a10cef2fc91f10b7f08af1c10858e287e019563f338d/rpds_py-2026.9.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:1d77b649e6f7cdf12ca5c2a98dad0ad37f9ea9b6f960408a92f0cb12bb3d04d9", size = 347463, upload-time = "2026-10-04T16:30:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3f/ad45d03df4f84ebae5439577ee81f3999d182711e82235c8037b6528890e/rpds_py-2026.9.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:00ba2d8c7dd4ee537978ddf4b3fbd712bef2d8751603f7f3146b3f4287768e25", size = 342051, upload-time = "2026-10-04T16:30:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/3dcd68c13d4bcc59c1f7eb33ac8e80698f06f3eb0d8a1e06419836071c20/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec450527cbf485e13c8d3602a54f428ab0432fdade0ede75efd74b735421c871", size = 374143, upload-time = "2026-10-04T16:30:51.508Z" }, + { url = "https://files.pythonhosted.org/packages/cf/0d/68c1f058a250fbd1380ebda9fc227cbf50117adf8ffe8161ef383ea79f68/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:306ee1850d8105b5baf977e78d45fcadd12c1a54678d614c9baf217708446e91", size = 378604, upload-time = "2026-10-04T16:30:53.206Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d6/2d4c59b85397cb4800594fadf692688ccf5ce556adc930e7a5bf21061a5e/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef6b65b03247c54692ad4fd9ee97cb772781927db72e3cb05e70b3db6d1ff14f", size = 490790, upload-time = "2026-10-04T16:30:54.925Z" }, + { url = "https://files.pythonhosted.org/packages/da/04/7e05dc3aebaf52f4e026766bd668fdd09a9d0e23f64a14686b36b3501892/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a575404ebc9cf2e91edd32eaf570ec1430eb900d4f56724ba7dd4bc1fc9c176d", size = 393146, upload-time = "2026-10-04T16:30:56.625Z" }, + { url = "https://files.pythonhosted.org/packages/57/ca/e2e9a0a46a74ed51a0498ba1fe10f4ea6b2d9a155f372a2f91e51f18cf10/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c16ab111bc27c646ba8aa005d0527754edc538ebb636f0b1bf8e244b48d1945", size = 372022, upload-time = "2026-10-04T16:30:58.295Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cb/8f8774df5134e23424372838bcc5c7ed4127d723e1ff52f7bebd4dcb2563/rpds_py-2026.9.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:7664419f27db41d4f1c43a78dccda7dd6e8ef2428df3ee01d0c2a07a6b071297", size = 377092, upload-time = "2026-10-04T16:30:59.984Z" }, + { url = "https://files.pythonhosted.org/packages/90/02/8d7095d73bf9114219be40230baa5df00611e0a82ed9517779ff9c19f82b/rpds_py-2026.9.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b26b03d9d2658ee2fa234f8f4f19f38a09773fe5261028025032e26d4d35af0", size = 400830, upload-time = "2026-10-04T16:31:01.721Z" }, + { url = "https://files.pythonhosted.org/packages/ec/02/8206856f8f363cd042a8315dc86b3912f5dcb6d3c66bbb24b69c2bfb0775/rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:be3e47e2d91aa3942ff9bf4077a505226005abfc39b6f7554a91c1b9393986b9", size = 551478, upload-time = "2026-10-04T16:31:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/41/6b/36211f1bb1f0b0313f496d92f5905b74ea107f27cb16fa3355a82f04575e/rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:6307a0da524939decb8ca4a3933b8ab62525794411d6984fca6726e732804af6", size = 615173, upload-time = "2026-10-04T16:31:05.281Z" }, + { url = "https://files.pythonhosted.org/packages/35/77/cda0c4a6f055446b692f0ed5692f73707cafd3dff82672ede31b1a9b59de/rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:159a7aab5c5e8b112c8830f54717ce56da1252ebdbb526f5be2df2309280b9e7", size = 579653, upload-time = "2026-10-04T16:31:07.065Z" }, + { url = "https://files.pythonhosted.org/packages/74/ec/d8385f446240aed643b9e92a5055cff3015cc04a13c61f73b2883c478ed5/rpds_py-2026.9.1-cp315-cp315-win32.whl", hash = "sha256:dbc2673f9223d420c91145599b3ba45a8a50c207d1976908e5fb5ddb0c9b9429", size = 206498, upload-time = "2026-10-04T16:31:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a5/71b5cd00e0521e3b6b81828baea368c62b6b700ebdd9554cd7d41ddf12fa/rpds_py-2026.9.1-cp315-cp315-win_amd64.whl", hash = "sha256:75c38c50ab9aca840225d9a9a3810bf11d04bd5c1f186cabbb8aee56db3e9b15", size = 223121, upload-time = "2026-10-04T16:31:10.84Z" }, + { url = "https://files.pythonhosted.org/packages/33/58/dba857c3bc8221b31b62eb170a3080f4f191de79f047200389b7ed1b06a7/rpds_py-2026.9.1-cp315-cp315-win_arm64.whl", hash = "sha256:a431156bb41865fc14cd5d79bb9d7bbed83110b0159e34e62ae30951f96c0009", size = 218741, upload-time = "2026-10-04T16:31:12.592Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d0/320ab28ccc1415eeb509d68682b0014fb74690cd49f1c2d29a232475af50/rpds_py-2026.9.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ef0d8c843e2827d6c120ab4687e9423fb1d893db1df27b7c1506615bcb9734a0", size = 349719, upload-time = "2026-10-04T16:31:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/56/88/f5b12f1358f443c08b7ce3cc8391d82d335f4872580e2e097847fd36087a/rpds_py-2026.9.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:45bc6bccf78b20fd834237d18db64965d7ee68ba7f60440a26c7ab71e7b8d51a", size = 343267, upload-time = "2026-10-04T16:31:16.827Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0c/c765b0059d532acb3b9c45d781ccc22f15a96dbe443d00903f643ba9df10/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d55198263bb51f557550c6ed2e6d1cb6a6fed6eb5c9120b741c5926bef8a45d", size = 374555, upload-time = "2026-10-04T16:31:18.931Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8a/cafddfda77564a10cd21184640c3bffda6a2b8d20972fb5dedd5e0166328/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8763f20692da7df39b0afdd1ba3042b004c50a45994f76c2d9a25641f7673db", size = 378346, upload-time = "2026-10-04T16:31:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/b9/01/5e626016eff72c183bf6c96539240cace15d402468647a453ec08415b2fc/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e43d4a1f673e8a1cbd8533e809e02b4bf9d4f2280269bb640436556312121250", size = 492373, upload-time = "2026-10-04T16:31:22.614Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9c/15a2469e9389242f46896b3f0a01d68caea8a5a35c011fcb05ef333aae73/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea394a937f17a54c51239348bdbe2e3518124c8d4a8951ba04a311d3095bd18f", size = 394371, upload-time = "2026-10-04T16:31:24.768Z" }, + { url = "https://files.pythonhosted.org/packages/63/f5/c100ff77e1e6366e947c75969c258fdfc4b7bc5bbfe7351e62ffbf2a1228/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdeaa99ce822dca76cfb1b993e9120c5ea212f2eb66d48950ad63c349668a018", size = 375132, upload-time = "2026-10-04T16:31:26.588Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/fe0269c9de253e99c81cabc12b8971a5feaa083debdaff1221e06264d9e3/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:b4f062343e7ad3fa94f2c66e5ae667dee47ee74dd41a9057c4fbe163236a123d", size = 377642, upload-time = "2026-10-04T16:31:28.677Z" }, + { url = "https://files.pythonhosted.org/packages/05/65/b34a7b257baccff8f4a24a722933166d4941d5ebdc9c3f4bc4ffcd5ce4f4/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22ffd29a63d71fb1b81552c21f2c2b734949b7ac751a9be70675a939a900839b", size = 400855, upload-time = "2026-10-04T16:31:30.802Z" }, + { url = "https://files.pythonhosted.org/packages/98/32/844e54176b6071b90b38a564e6940bc6eb8f97b2890dc709c19db9dec0f4/rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:08dae4a4095150a7c4545a1fb40b98e1ab1744fbc2770d92c977b9dadaa49ab6", size = 552573, upload-time = "2026-10-04T16:31:32.709Z" }, + { url = "https://files.pythonhosted.org/packages/17/73/6041d20729dffbfdf155c02d65be58bc225a1c1fb548fd87c23ef306138f/rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9a0460d43603d1fd9ef59c30278531e15d78581721ddb538fa560aa7817ea4ad", size = 615840, upload-time = "2026-10-04T16:31:34.565Z" }, + { url = "https://files.pythonhosted.org/packages/af/9e/418094adaee6b056ce199051b255448ed872829051e341b2294c80da0977/rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2d1f6da5128eabf34e963d7163a818846075a52568250d006c4c953b40f903", size = 582764, upload-time = "2026-10-04T16:31:36.665Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9b/1698ebf6b840ddfe8b472198abbed6ace35c6e398faeecd6c47dae742a4e/rpds_py-2026.9.1-cp315-cp315t-win32.whl", hash = "sha256:5c6ee90dee3e85e055ddfd502d611643d9b0fd94c818220bda84ec3dacd9b27b", size = 203795, upload-time = "2026-10-04T16:31:38.53Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e2/91f70d804c61f8eac39a417e82aa1024f655ab9e8bdd7393b196242bc41b/rpds_py-2026.9.1-cp315-cp315t-win_amd64.whl", hash = "sha256:fe5ad0664ec772b02c45859041aa17655709cced7a31005817fbbbd988c25567", size = 223037, upload-time = "2026-10-04T16:31:40.468Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/f0eba548e297987964ef8088403db875b333963bf8189f3f563b6936b20e/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c0d2cb595a420b34d5086db0add011e26e2c09d6a024afbac4228bf8f863a30", size = 384112, upload-time = "2026-10-04T16:31:42.643Z" }, + { url = "https://files.pythonhosted.org/packages/33/89/891fa4d81f1eae7fe262ecd0aba2c51de94a116c7f3ccfb034fa75f4c537/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3d6ed6a98cfd19155996605474982cc470d7601746a6439078f1a5a3fa8b050", size = 389662, upload-time = "2026-10-04T16:31:44.642Z" }, + { url = "https://files.pythonhosted.org/packages/4f/46/912cf623b2263f9c9c2d04e8508fc38dbe89983faeba5fb36ca4558ae550/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8171b44a054e5c67fd748ada04187f1250bf35b95f85e52ab64bcf3331a923bb", size = 504637, upload-time = "2026-10-04T16:31:46.667Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5c/7f3cfa7c5f01765c9f47c8398210ed0c1eb686c0e99c223cb1c6776d3fb9/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fda1d96e542c37b6c804547dbf489c129fe7c97183a76a5ec275909ba1a063df", size = 402607, upload-time = "2026-10-04T16:31:49.068Z" }, + { url = "https://files.pythonhosted.org/packages/f0/76/2e915d4115ee453c33e6af17bc1df7e22ddc52974e178eeeb92a78c09983/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270bdcdaac5d5b6f73c5e22e7e135c7f2a50e789f71d9e241d5be8d90026e19a", size = 382762, upload-time = "2026-10-04T16:31:51.24Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e5/844382925d7b84a4074ad79fc2b9d05eb6231ea243b34f247e68e519b606/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:84a6ecc0c940169190d2c23bd969debd48c94dbc855acd60188a68d71d421608", size = 389563, upload-time = "2026-10-04T16:31:53.549Z" }, + { url = "https://files.pythonhosted.org/packages/81/82/d6e3951602dc2e24790ef9d299aa24bf5e2493df5043f2c6da00480cc447/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab4b2fda7c2b542f7f9d886cc6a838c5079d2b76f72e6081411faba11adde2c9", size = 416842, upload-time = "2026-10-04T16:31:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1c/8d248e4ea671d525ab7daa0703ad81cc2175076fa0ccd7ef088facbd2b01/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:44b32a7c4f0da3d28af31c259e38ddcff096f855e205ed0671d02fcf44f1ea1c", size = 562434, upload-time = "2026-10-04T16:31:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/ad/38/e7ac0db898120d15dea25e3661b1242a40cb3b23d1f4db86c15a01fe0ba1/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:a3dbc5ed9514908d5046107d7b1346bde71eea61de6e0e4919c19354f97e769f", size = 627909, upload-time = "2026-10-04T16:31:59.977Z" }, + { url = "https://files.pythonhosted.org/packages/03/c8/37e555514932cb1ee36c75349187aa882a98dee5d5654c761699ed8a0d27/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6eae33003518fd4cb4f83a218d5371469dd3001aa3b87128c005b07762f7fe5e", size = 593658, upload-time = "2026-10-04T16:32:02.14Z" }, + { url = "https://files.pythonhosted.org/packages/12/5c/5f69a91add94a8a33acea68a2b034a464946a654ff7296af9d957ef0d6a5/rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_10_12_x86_64.whl", hash = "sha256:0483515261947e4e8b8e1375bf7463e7eb6ccfb3d86e7b554d90cd5285f20f32", size = 351769, upload-time = "2026-10-04T16:32:04.675Z" }, + { url = "https://files.pythonhosted.org/packages/41/22/35ea8bf3b2682b90ab687c74e4828418ff12298a5b0b0a0c033f61c86a30/rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:4cfaf02209061880210819934de2f4f6aa83dc04dafe6770276acc240a56da31", size = 351719, upload-time = "2026-10-04T16:32:06.834Z" }, + { url = "https://files.pythonhosted.org/packages/a3/90/a9ba81408369d34c1aca73319c15adc91ed45c1d21e9e8ee4832ffb7fe0b/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdc537c8633d7fd92a82e2e0d2ab74320a3f63d5e59fb9cf08711e08fe151c4", size = 381109, upload-time = "2026-10-04T16:32:08.994Z" }, + { url = "https://files.pythonhosted.org/packages/b3/44/5192a0bed94cec86bd2a5e1cc5a1bb8f7eec3aec907eacd2deb13e87e326/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10e208f2425d973938afcd56e28a7c4be32e27b6a60b5d381f49fb9d8acf9759", size = 388222, upload-time = "2026-10-04T16:32:11.321Z" }, + { url = "https://files.pythonhosted.org/packages/92/cd/5356549711448f18b52f7a11ffbe90f1228774996fbf3c6cd1b18d22abaf/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c0dbbcc19735fe5f8b0a54c07659d154a9e69f47e15d0a6ab7299215daf62cb", size = 500384, upload-time = "2026-10-04T16:32:13.478Z" }, + { url = "https://files.pythonhosted.org/packages/7a/88/ddda9d28adfe33119c75b2e733d4ba7e326f41d7e1b1093951771768ca48/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684fd492fff4fead00587544e059be2bbcb6f93454f21fa2a91b66fc7508be82", size = 401661, upload-time = "2026-10-04T16:32:15.653Z" }, + { url = "https://files.pythonhosted.org/packages/32/c3/bb59ba16a6b4a57995d15b8c04c00f28df3b938c4dcdde48fbe0f73edb0c/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1028417bb44037eb3069c1009bd7b7277212876cda22fbe565b0bca9fab6d2c", size = 380748, upload-time = "2026-10-04T16:32:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b8/0580faa1c5a32ddc160130dd7ae54d2d007272a89a40a6b5db3e50f9f585/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:492e5e428cbe126221611f47e068f01660352feec4ad18bc0f5ea9b2ae88fb14", size = 385731, upload-time = "2026-10-04T16:32:19.855Z" }, + { url = "https://files.pythonhosted.org/packages/bf/70/f73564642bbe3322c7eeef2c2f258cf040b71c41a430b532e59d04a1b838/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:88b5268892fde430d5531f95bc560b6efbbd67c929662c586afd729a96e7461c", size = 412131, upload-time = "2026-10-04T16:32:22.285Z" }, + { url = "https://files.pythonhosted.org/packages/8c/5d/17fff2e1f8f68721a2afb5cb48f7e442c751bb6b4883157c5abaf618cd08/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:01445c8d194aa032a08e944f16567672da1c62dbdbefd8b6d0693032e290cf68", size = 558193, upload-time = "2026-10-04T16:32:24.4Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d0/869ab6fb08531f97aae4a780b4d61b4190d8aef48fe054ee28bcf7114466/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_i686.whl", hash = "sha256:eef6a03b0b6d08d0835ccfa8ec8d1bc70525e3801387567137b50c557695e6da", size = 622448, upload-time = "2026-10-04T16:32:26.844Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a5/9672e532fe02cd3b92c78cfd8743abca939d96853006c63835009a1ecb6f/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6b9bf3135b4ad5981df9a73d71a35272d650a2985ae9c2746357b24d59de2448", size = 591126, upload-time = "2026-10-04T16:32:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/80/ef/3a9f8e4279c7920af561deed4cb7ebebed2794a8f608cf404d5eef14a105/rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_10_12_x86_64.whl", hash = "sha256:56c6952a9b15047466d0c2347c446a761d4527f89976156341e68f0ce5cc08b0", size = 357108, upload-time = "2026-10-04T16:32:31.641Z" }, + { url = "https://files.pythonhosted.org/packages/b7/55/4b2fa381a583760aea5c92841e4e928a358e1c6511b129219e9c2826a226/rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_11_0_arm64.whl", hash = "sha256:b242c27c8f836305a4a72df9cdd564386ac57b807bd252a063223331c9316b37", size = 346877, upload-time = "2026-10-04T16:32:34.061Z" }, ] [[package]] name = "ruff" version = "0.16.10" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10.tar.gz", hash = "sha256:eff4728c4eaae93f0955cd264d24b2ab348e74bf59986ccf282ba6dc16b3b017" } -wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-linux_armv6l.whl", hash = "sha256:488b0fe3f3574210e5cf80d9f59b9e3ab17a127a8155de3f307b392589cfb511" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e748ff95c934c4e978783b8e687bc174e7bd84e8ad24e3243e1ecfcda5e0282d" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3031a4a2e8e7b8a46f70be45f198c35a11ece509a94b80334d8d397a33c67550" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:494401c86df4c4c25f69b9419605d944467ee98c42fb6ad405ef4fa40b8fb67d" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d203abc0ff2b773ee33d00ab8df0bb67046fbc7c07b119332f08b9b345cf8221" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd83d1235a5258d318477bc5b576303974cbdef5df0c01a1bff14efcc23bd12a" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc2610fb269fa56dd8a68669ae470fa6272902668c0fc2ebc3aa112b2633d5b8" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e70175e29cc94c26ea296c80e470180b744b7419026898e58f520c6ab32578e" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f33f43a864a8483eebd160e713336c8bab02c934feaff0a33cf5ccb41546d09a" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1dfc6f0088149fb6a362c1c446bcbb3fd2157b3852fe2fa68409276eab9ad9b3" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6553498afc35f580f036030795810b9e6bcea31604b0fd9e8d352795473042e3" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a3b8471dea115d37f123882be852bed13403746d3a76c11de4a19ec5f9ff5a03" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:92e59a70bcbd9d3a5483656da906ec28edfdacfce00afd99edb8b4e9d15644be" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7ae7375f803b5520dc9f546bed7e3a0acb70b91812e9bb4b19927de22f25b77d" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-win32.whl", hash = "sha256:2a12e01cb9156c10c466f63b46eaae5ecea28dfbd21b5836353ae498e7d1349a" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-win_amd64.whl", hash = "sha256:97f2015c92aa97105b0eab19eb5d224884399281cfc5da86a92db4ab5e7fb2ca" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/ruff/0.16.10/ruff-0.16.10-py3-none-win_arm64.whl", hash = "sha256:25a65fe998c4e6861ec079ada5826a2fc605e6cbccbe9dcd7fac1f54e791621b" }, +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/49/23802c45f093eb14bde54b141d2b2f058edfa63a06db7beed047308cc08f/ruff-0.16.10.tar.gz", hash = "sha256:eff4728c4eaae93f0955cd264d24b2ab348e74bf59986ccf282ba6dc16b3b017", size = 4958724, upload-time = "2026-10-01T18:03:21.697Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/21/ebce22e1d90cdb2cd691026b9c6e9bec6499f0b396089481755b5d49efff/ruff-0.16.10-py3-none-linux_armv6l.whl", hash = "sha256:488b0fe3f3574210e5cf80d9f59b9e3ab17a127a8155de3f307b392589cfb511", size = 10095557, upload-time = "2026-10-01T18:02:36.072Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/a54de85876a8b2612bfa0d84c7b9abfb39c6a3354aee7800b09c1649b9e3/ruff-0.16.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e748ff95c934c4e978783b8e687bc174e7bd84e8ad24e3243e1ecfcda5e0282d", size = 10340577, upload-time = "2026-10-01T18:02:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/9f/16/1a5a4a2657effe4806110f2b907313802f1367fcdbb29e8122766e407fab/ruff-0.16.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3031a4a2e8e7b8a46f70be45f198c35a11ece509a94b80334d8d397a33c67550", size = 9774282, upload-time = "2026-10-01T18:02:41.79Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/470085af734da396e68cd80fb0a3e7c109a459716ae59588c0f6fab8a17d/ruff-0.16.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:494401c86df4c4c25f69b9419605d944467ee98c42fb6ad405ef4fa40b8fb67d", size = 9920895, upload-time = "2026-10-01T18:02:44.458Z" }, + { url = "https://files.pythonhosted.org/packages/57/de/f10cffe4f88a37ea6615ec460f01bf76f0bc1477737a35d9ee61a630a78b/ruff-0.16.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d203abc0ff2b773ee33d00ab8df0bb67046fbc7c07b119332f08b9b345cf8221", size = 9902707, upload-time = "2026-10-01T18:02:47.041Z" }, + { url = "https://files.pythonhosted.org/packages/ff/44/3fdcedf83ae60ef837dd239170e480dce606a9142cfa2db911afdf855fd9/ruff-0.16.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd83d1235a5258d318477bc5b576303974cbdef5df0c01a1bff14efcc23bd12a", size = 10619287, upload-time = "2026-10-01T18:02:49.485Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/02e76a5574002153618eafbb70e159468addc72a5d2c488e65cbb4639d2d/ruff-0.16.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc2610fb269fa56dd8a68669ae470fa6272902668c0fc2ebc3aa112b2633d5b8", size = 11339942, upload-time = "2026-10-01T18:02:52.008Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c4/cde27d47ad8d4126c587e608c67c46e7feac11763f606d261a1bc8a489e6/ruff-0.16.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e70175e29cc94c26ea296c80e470180b744b7419026898e58f520c6ab32578e", size = 10934316, upload-time = "2026-10-01T18:02:54.811Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/17234145f302a645a123e8c3bb2411ecf4669fbc350de3b0d803230a1729/ruff-0.16.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f33f43a864a8483eebd160e713336c8bab02c934feaff0a33cf5ccb41546d09a", size = 10387968, upload-time = "2026-10-01T18:02:57.497Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/2493af60240ee7644b38a4b821f5f9c3b5a4fa3178770fe1d0c685217395/ruff-0.16.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1dfc6f0088149fb6a362c1c446bcbb3fd2157b3852fe2fa68409276eab9ad9b3", size = 10537906, upload-time = "2026-10-01T18:03:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9a/f56b28f3b143bb9e518c34fb89ab191b626bca8b70dbf8af0d2e2d473572/ruff-0.16.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6553498afc35f580f036030795810b9e6bcea31604b0fd9e8d352795473042e3", size = 10012938, upload-time = "2026-10-01T18:03:03.006Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/fca37362848ea4d4d80712df13632e645e7c7cfb1bdedf140699ac7a090b/ruff-0.16.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a3b8471dea115d37f123882be852bed13403746d3a76c11de4a19ec5f9ff5a03", size = 9897945, upload-time = "2026-10-01T18:03:05.818Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c7/e0bc57664d6e0c61fd22f620af260fe9662d7e1ddb320ea7f160e76ef165/ruff-0.16.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:92e59a70bcbd9d3a5483656da906ec28edfdacfce00afd99edb8b4e9d15644be", size = 10333134, upload-time = "2026-10-01T18:03:08.25Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/5c9f3b68737c0e4a33a1db5dfab44f7b786d96ca91a7233112ddab1e6dc2/ruff-0.16.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7ae7375f803b5520dc9f546bed7e3a0acb70b91812e9bb4b19927de22f25b77d", size = 10741702, upload-time = "2026-10-01T18:03:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/78/fa/0f9c2020dc316c53d983be011720b8157cc052b97e3f4dfa7db6d0f880a6/ruff-0.16.10-py3-none-win32.whl", hash = "sha256:2a12e01cb9156c10c466f63b46eaae5ecea28dfbd21b5836353ae498e7d1349a", size = 10139176, upload-time = "2026-10-01T18:03:13.267Z" }, + { url = "https://files.pythonhosted.org/packages/99/29/cfb0df9448d4d4ad48c2de029ada9ebd71baa6da983a6c77ee6c6cd0fe82/ruff-0.16.10-py3-none-win_amd64.whl", hash = "sha256:97f2015c92aa97105b0eab19eb5d224884399281cfc5da86a92db4ab5e7fb2ca", size = 10584734, upload-time = "2026-10-01T18:03:16.006Z" }, + { url = "https://files.pythonhosted.org/packages/fc/05/c16957eb287c3fc062e032619a25868d93d408a844b725bcf514f7a378ff/ruff-0.16.10-py3-none-win_arm64.whl", hash = "sha256:25a65fe998c4e6861ec079ada5826a2fc605e6cbccbe9dcd7fac1f54e791621b", size = 10366440, upload-time = "2026-10-01T18:03:19.04Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.19.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/c5/0a5ce655109cb96b8dfad36908a4e108d15f5dc2eff8fbc3146c4312e704/s3transfer-0.19.3.tar.gz", hash = "sha256:8326acc3d056d7f7b8a37110c7d2aea41f67e83f6ae79cd3b5fbcca34c726406", size = 166087, upload-time = "2026-10-19T19:19:49.751Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/a4/88f9e10498759121060b8050a1554753dfc04df39bbbcc7aad9ecc8d9440/s3transfer-0.19.3-py3-none-any.whl", hash = "sha256:24eb7535804306355e31c952a90022bf5b2f9d760f9a11062c7493a217c08553", size = 90252, upload-time = "2026-10-19T19:19:48.309Z" }, +] + +[[package]] +name = "scantree" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "pathspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/e4/40998faefc72ba1ddeb640a44fba92935353525dba110488806da8339c0b/scantree-0.0.4.tar.gz", hash = "sha256:15bd5cb24483b04db2c70653604e8ea3522e98087db7e38ab8482f053984c0ac", size = 24643, upload-time = "2024-08-03T20:08:59.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/ce/828467ddfa0d2fe473673026442d2032d552a168e42cfbf25fd0e5264e0c/scantree-0.0.4-py3-none-any.whl", hash = "sha256:7616ab65aa6b7f16fcf8e6fa1d9afaa99a27ab72bba05c61b691853b96763174", size = 20690, upload-time = "2024-08-03T20:08:58.137Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "shortuuid" +version = "1.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/e2/bcf761f3bff95856203f9559baf3741c416071dd200c0fc19fad7f078f86/shortuuid-1.0.13.tar.gz", hash = "sha256:3bb9cf07f606260584b1df46399c0b87dd84773e7b25912b7e391e30797c5e72", size = 9662, upload-time = "2024-03-11T20:11:06.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/44/21d6bf170bf40b41396480d8d49ad640bca3f2b02139cd52aa1e272830a5/shortuuid-1.0.13-py3-none-any.whl", hash = "sha256:a482a497300b49b4953e15108a7913244e1bb0d41f9d332f5e9925dba33a3c5a", size = 10529, upload-time = "2024-03-11T20:11:04.807Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "starlette" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/eb/4e197bf26b023abafc5e9043ec8710d7de0fcb032d0969045236d72aa0b1/starlette-1.9.0.tar.gz", hash = "sha256:3d931de246ed870e35ce605974440816ef895e592f255d1396cfdf191d4c4318", size = 2732466, upload-time = "2026-10-18T15:50:33.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/93/181df62699d9b5aca1d87e5cfda131dac87e3cdaebf668662f7206ca3470/starlette-1.9.0-py3-none-any.whl", hash = "sha256:fd3f159936e882d06c1108f84688f7cda2cf85fc6a592a50edbd175e882c2ddc", size = 80335, upload-time = "2026-10-18T15:50:31.526Z" }, +] + +[[package]] +name = "storage3" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, + { name = "httpx", extra = ["http2"] }, + { name = "pydantic" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/09/8ffcd57f51542f7d1eb917488de2255c879227d363bea764ae0501d775ad/storage3-2.33.0.tar.gz", hash = "sha256:7b5a27ca1e64e324420ca9514c38317f243a0ae7a56aeef336976034c1c565d4", size = 20166, upload-time = "2026-10-15T19:14:21.516Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/78/98ff52ddd24177ca72e0bbc7527fc8391df187304f32de2421d20b55f1b2/storage3-2.33.0-py3-none-any.whl", hash = "sha256:6e357ea7328961bcecc02a35fac8fae555b0c4b6b02ff553a956824693fd4cec", size = 28469, upload-time = "2026-10-15T19:14:20.459Z" }, +] + +[[package]] +name = "strenum" +version = "0.4.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ad/430fb60d90e1d112a62ff57bdd1f286ec73a2a0331272febfddd21f330e1/StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff", size = 23384, upload-time = "2023-06-29T22:02:58.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659", size = 8851, upload-time = "2023-06-29T22:02:56.947Z" }, +] + +[[package]] +name = "supabase" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "postgrest" }, + { name = "realtime" }, + { name = "storage3" }, + { name = "supabase-auth" }, + { name = "supabase-functions" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/ef/67310d1a638c36cbe09fe73afc5b9f09e9cd5b9b55e9744d90e35e6be4a1/supabase-2.33.0.tar.gz", hash = "sha256:7834e2b6373773c8105449cec3089a0dc1bc4069505be057a13404cfcbc42f8c", size = 9820, upload-time = "2026-10-15T19:14:23.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/48/1a8f57fdaf882f1dcd7836352e33829c1d455c6cfc387d2b0fb1e99ffdb6/supabase-2.33.0-py3-none-any.whl", hash = "sha256:57c3aeeac3d612321deac8176b935077a7643461c8df4bf28722c2024c87ccc6", size = 16753, upload-time = "2026-10-15T19:14:22.26Z" }, +] + +[[package]] +name = "supabase-auth" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", extra = ["http2"] }, + { name = "pydantic" }, + { name = "pyjwt", extra = ["crypto"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/9b/a6093da7497561900a1102958a7d02d694441704ea4bc89d0f87a3229a33/supabase_auth-2.33.0.tar.gz", hash = "sha256:832d3097bad72cf5bf70b55ab776385f8bad2c17a9c37b858e8bb7a6abe7c4a8", size = 39316, upload-time = "2026-10-15T19:14:25.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/cd/8fadb81917823bf416290fad4e652f44357572b52c8ce86be0d26bd19dab/supabase_auth-2.33.0-py3-none-any.whl", hash = "sha256:d48f361309cb5e8eeb7babfd0f326e1a81870f27a0ba15f94194fbab13153f5c", size = 48534, upload-time = "2026-10-15T19:14:24.247Z" }, +] + +[[package]] +name = "supabase-functions" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", extra = ["http2"] }, + { name = "strenum" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/42/de737739a908cbcdc86389f4d5ee2ca12abaa780e1560c50d290a1113739/supabase_functions-2.33.0.tar.gz", hash = "sha256:1563788b6d15c29c1aee77c7e6fbdb1a8f3e29e4a8b3231e82e01f825ed00acd", size = 4996, upload-time = "2026-10-15T19:14:27.352Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/4e/30a13a3b58a97828faaf9df2af0947f9ff4a2585289dcd17b2c95f6d2c72/supabase_functions-2.33.0-py3-none-any.whl", hash = "sha256:3a8531f5ac7519535fb0032fa4490220d49d338e83608d38d1b178e9c7999414", size = 9269, upload-time = "2026-10-15T19:14:26.253Z" }, +] + +[[package]] +name = "tenacity" +version = "9.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/9e/497c1c8ebe5a5b5d1d4a7511aea22c0bb1a97e3170d98abdef0e1b34265a/tenacity-9.2.1.tar.gz", hash = "sha256:a606b5c808d0cded4a359d5b9932d867ff2a6a6b64d37350260fd01bbdf83839", size = 58261, upload-time = "2026-10-07T12:13:01.633Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/26/1ff2b0721ac66a3ec5b1402b333110b352ab0a8724052ac279a7b82d40c4/tenacity-9.2.1-py3-none-any.whl", hash = "sha256:9e56f17539296baab7beabb08b92f6ee3d7be92d8be72d763360677c2ad6580e", size = 32310, upload-time = "2026-10-07T12:13:00.102Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/62/167a842aa0429d45f5e797354fd4343a96f6043d67d0513c675c7b8d36e6/tiktoken-0.14.0.tar.gz", hash = "sha256:231dec90efcdccf1b565a1416107736f1e09b1a08fe736ef9d6363e626d03874", size = 38898, upload-time = "2026-08-17T19:49:49.514Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/82/d60a7a5d7bff7b4641d556ea68ea5914ea6edc3774a12eb1c0d444701382/tiktoken-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3b12e54f8bec91433e41aff65d8d1f209a4f678081163747079806e5361f6c91", size = 1095817, upload-time = "2026-08-17T19:48:31.788Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/d39ae33d3dc30a0c229ff0cb683df961ebb5e7b8691feb2d08b3ee6ac327/tiktoken-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:94f77b60a8ab23580db19ae822744c9716c1720020d2179ca5605112d12326f1", size = 1043064, upload-time = "2026-08-17T19:48:33.138Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e9/8e18cbee0c3ae8321c7e9696bef6090a24eed99a4a75a4c4a7f5115e5a2f/tiktoken-0.14.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:f3d6cf93fbe2e7117eb7bedca684216fbe328a41f0843ce34245451d8eb2df1c", size = 1190381, upload-time = "2026-08-17T19:48:34.386Z" }, + { url = "https://files.pythonhosted.org/packages/af/c8/051e7b72a816ff50eb34a1c7c5b185cd2429ffdf59a497baea35b2b6b2dd/tiktoken-0.14.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:18a1b651c4b032004bf7b4f1713391a54b2a341a52c6e8a2b59acae9d16e13c7", size = 1206869, upload-time = "2026-08-17T19:48:35.581Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b3/7795db206adb6a57d6137fe48ef2cca6b9707e90b86ee8244671592ddc33/tiktoken-0.14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4d8d91d68353bd167fdf26467e5ff9e56aaa5f87d6410c0238608629e4dc0d33", size = 1255197, upload-time = "2026-08-17T19:48:36.832Z" }, + { url = "https://files.pythonhosted.org/packages/c8/39/5234783af6b81af645ccdf9438f2f02af472f14e91d876ca2079af641841/tiktoken-0.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10f31e63e40313f2e518d87f7086cfa44e45f64cc14d8ae14103b41220c30a14", size = 1319329, upload-time = "2026-08-17T19:48:37.944Z" }, + { url = "https://files.pythonhosted.org/packages/88/cf/f2d955c8c5c6c67cc86ba6fb132c47c710465ebe6a6dcec1c3b6e250660e/tiktoken-0.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:c6cb9896a82b9ee44e15ba0b5c8044072f2e4d48acaa704c8d3feeef5ad9487c", size = 944146, upload-time = "2026-08-17T19:48:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c5/9d848b7f408241171e1f843deb8bfa626086452bc9c78beee500829583e3/tiktoken-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c2edf09b381fafbc014ae8e018ed25087abb9a3dafa8465a0ea63c6558c47a79", size = 1094971, upload-time = "2026-08-17T19:48:40.347Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a9/d94302340304328961d6f0c35ca4e60617fbb57a5cf667e2ed1692cb9e57/tiktoken-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cd8ca1305c1c902fe42c486165f2e4808d9997625c98ffb05b9e0366d99d3948", size = 1042916, upload-time = "2026-08-17T19:48:41.541Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b6/31da98ee871383509cae2ba96a9ddef1965e3c4f8cb6dc7bcda3379398db/tiktoken-0.14.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1f83081065ee5833d35b49e9180f3d8d15622a603dd1c435da0da6cc12b3662f", size = 1188650, upload-time = "2026-08-17T19:48:42.729Z" }, + { url = "https://files.pythonhosted.org/packages/24/65/8c5dddd7cb67f6571d154a58d7c6e2f07da54bf84c49b6a1839965b7c35e/tiktoken-0.14.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7665f6624e052e5e7f6a36919ab69279decdc976d7b16b4fa15e1897d0513", size = 1206378, upload-time = "2026-08-17T19:48:44.013Z" }, + { url = "https://files.pythonhosted.org/packages/d1/04/522ec59d30dd9a2f3ab837011cd4fc5d1178dc4a2fa07c9fa4b90af6ba9d/tiktoken-0.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:144a3fc369f92b7d548995217c5d6e84038d3572157a0f6f34080d65291d0f78", size = 1253694, upload-time = "2026-08-17T19:48:45.597Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/9019e272bad188a1c61ecf44f25a9ba2368744644e3ac1f3d6516f3c9e80/tiktoken-0.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:151d37a150c8f3dfc5f4345597b10e101876bd1bd13494e0185af6b508758d2e", size = 1317873, upload-time = "2026-08-17T19:48:46.792Z" }, + { url = "https://files.pythonhosted.org/packages/24/7f/fff1217240343c0c11b5938b98aeae0e3a266cacfac25f86f91cdcd748f0/tiktoken-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:c77d4a3e1deb2707819df92046b89aad1ac81d27e07616b797cbff3f62c037da", size = 944395, upload-time = "2026-08-17T19:48:48.028Z" }, + { url = "https://files.pythonhosted.org/packages/8c/da/e273746b9d24a63c776bc60fba914351573ad9c575b52601eb5e60632564/tiktoken-0.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8e947aefe98ef74cce94923f90e48c98fe34eb1ec0a6bfdfadfc5a96359bfc36", size = 1094408, upload-time = "2026-08-17T19:48:49.269Z" }, + { url = "https://files.pythonhosted.org/packages/69/9f/fe6b1aca23331aa5271df5a4bd07bf68a7059254d47faee1b8272592a777/tiktoken-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cebe67765569df3dafac8474e4eccf5c19d24140492567a5e58a11445732a4", size = 1038499, upload-time = "2026-08-17T19:48:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/0b/35/e9f47647c9e163bd1de30fe1a491669b7248cfc67b7404c35c009a701e1a/tiktoken-0.14.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:7db45b98e94adf4173a5cd7422b150999a7ee11ff847783a14f6e1b80cc38cb6", size = 1186355, upload-time = "2026-08-17T19:48:51.93Z" }, + { url = "https://files.pythonhosted.org/packages/51/11/9976ad86980a00cdef05e730a0127a2578a1bc6d11644d8d47246de2eb26/tiktoken-0.14.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7896eea257fe497a2b7134474d909156c6744ce8da35bce88011a960e008aa0d", size = 1204197, upload-time = "2026-08-17T19:48:53.18Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9c/7035b0bcfaa68d1ee4803fc5be5214ad865669b05bd20e7105ae8a18afc6/tiktoken-0.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b950248272f1b303dc32986396e2dccfa10cf6d1e83ec8f0bba1776660305482", size = 1250635, upload-time = "2026-08-17T19:48:54.392Z" }, + { url = "https://files.pythonhosted.org/packages/bc/1d/69cabf18bed7f4366da076735816abce0d4db3fae491ae338a6612128777/tiktoken-0.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3de75343041a1c57333b1e707ac8a9769738241d7d6a55d39e12cf84548337c6", size = 1316085, upload-time = "2026-08-17T19:48:55.525Z" }, + { url = "https://files.pythonhosted.org/packages/bd/bd/a2e884fb1402cba5be08836590320012b2d8ada0e2eef9911a64df4bcd2d/tiktoken-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:087538c080e5ff421abd3a0785ed63c5111d06af98e6cd0d374dbe5969147ca3", size = 941208, upload-time = "2026-08-17T19:48:56.938Z" }, + { url = "https://files.pythonhosted.org/packages/50/53/ee1453623bf65f019328721ccb6587846d2c5b7b82f34e73ca09101f072e/tiktoken-0.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9c5fe393aab56469f04e432ff851216d3def3436cf5f07e442a240164bf500f", size = 1094198, upload-time = "2026-08-17T19:48:57.955Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5f/6448cfe278c3664ba9ec5b5ac08344341f7dc3d42888476e215a14eda2be/tiktoken-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbe2cc3bba939bcdaf103e03df9d5039d33887080b315624be28ec69059e5f94", size = 1038820, upload-time = "2026-08-17T19:48:59.015Z" }, + { url = "https://files.pythonhosted.org/packages/69/3b/d67eac1bcce9dee3abe23aff5e3ded3116bbebaf67b80a0811c06d3806fc/tiktoken-0.14.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2157f52e4b4d7ac5ecc7457b3716834706e7ef9a46f5144029bfeb7cf71f4e06", size = 1186175, upload-time = "2026-08-17T19:49:00.068Z" }, + { url = "https://files.pythonhosted.org/packages/37/62/cae690d9783146b0f81f564ada0f8f611de68178c0c9c7e1e969f0516b48/tiktoken-0.14.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:26e60f6a956ee171ab728b37b8439905d7ea1db435c30f9822f291e9861c861d", size = 1203884, upload-time = "2026-08-17T19:49:01.163Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1e/633e30237b94e383cf814145499079f3bb9cdd4aeafc1bc42e01b0f810a6/tiktoken-0.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:380873f330b741c4435574f37edb20813d04603ace2d53e0a63560e1fec83010", size = 1250980, upload-time = "2026-08-17T19:49:02.274Z" }, + { url = "https://files.pythonhosted.org/packages/cb/56/4c12f07b812f84206f38d723eb1ebfdd34bad9309b5dbc0bee6bbcff4cbf/tiktoken-0.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3fd7c14b1cb45b486c39fc9b3443bb341f3e2fc7e6f31247f3435a5836651632", size = 1315434, upload-time = "2026-08-17T19:49:03.434Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e0/c65603f0c44811def666d3fbf611bf2af3b5e1ef613e06c19411419830b3/tiktoken-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:90a762670c7f968184723769a06ed51f5cf5ce5dcd1e30164f25c72d85c2d1f1", size = 940883, upload-time = "2026-08-17T19:49:04.583Z" }, + { url = "https://files.pythonhosted.org/packages/59/b0/1cf129f4af8fc513931f931023def596b7c4bfc77026513cd9d851da9e88/tiktoken-0.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e067f4cbcc5d036e8aff7fe7a6b530a8f4de2e4616ad9005a24a1879e24e6450", size = 1096273, upload-time = "2026-08-17T19:49:05.807Z" }, + { url = "https://files.pythonhosted.org/packages/62/85/2ae74575e321148484147e10b53c3b1717c59ebaa9edb4fe18b1f5c055f8/tiktoken-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f2af4a336ea56d6c14f27741a0e1d8294a35dd0b038bcf990d232ebb54eb994b", size = 1040269, upload-time = "2026-08-17T19:49:06.943Z" }, + { url = "https://files.pythonhosted.org/packages/89/29/92a1120a12e4bcf2d5464350d1a91b68a433d63ce656bb7f806c27aec09c/tiktoken-0.14.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:f702e0aeeb6506e57687e881c59e844ebe8f0a6a097ddafe20e3ab25f387be4e", size = 1186101, upload-time = "2026-08-17T19:49:08.102Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7d/144af98dc5ad68108451a82e2f5a17f80e2663f5115058b8dfd215c1ad02/tiktoken-0.14.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e3442bbb2f0c588cec876061e37ae67b455b9df9978b003c8fe30e45f2ef5b42", size = 1204457, upload-time = "2026-08-17T19:49:09.28Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1f/be7cb06ab2108f612f3e92e7b76cf391e192db0db37a984616f0cc32aafc/tiktoken-0.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:979c1524f753b662b0f3cd261b135afe6659cce33caaa7a5ea00dd1756b3055c", size = 1251716, upload-time = "2026-08-17T19:49:10.509Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6b/81f158d0f90adb826cd704069c2129a046cb784a2a09861009519fc41cf4/tiktoken-0.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2cc19ac87b41c9493c9778ff5847f0c8bbcf5bd0ec6b87ce06c1c802adc8a771", size = 1315432, upload-time = "2026-08-17T19:49:11.844Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ec/f5fa35ec13f07279fdcaf3cc9c04bbb154ea591d23978651f2b672593e8a/tiktoken-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:eceeff0c62419bc78d4b6e70a4762a4d25df3ae8f2d5946e3853ce93e7a57098", size = 988046, upload-time = "2026-08-17T19:49:13.282Z" }, + { url = "https://files.pythonhosted.org/packages/68/c9/7756717408d3d0dfea3f046c9466144b28afde39ff69d5808f2475dcd7f5/tiktoken-0.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6eb94895c45f26bb8f5546e5fd8a069efcf6e3f108ea9d5cbe3bf6f7f3983438", size = 1096261, upload-time = "2026-08-17T19:49:14.351Z" }, + { url = "https://files.pythonhosted.org/packages/79/29/46ad8061f57bd9f8b2ea0aa82bf574e0f2aa040b0857a1582adba9957899/tiktoken-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:86951a971c53979ec857bd8c4a32dc227ab0fd33f6c12a3bd62d3fbf5f0bfcaa", size = 1040183, upload-time = "2026-08-17T19:49:15.707Z" }, + { url = "https://files.pythonhosted.org/packages/5a/7c/3184d17b868456f17b60b1a75f5ec0405618a43aa753336df341d8f11781/tiktoken-0.14.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:e2eca764c53490f8930dbce329e0769f11108d87d908282a80c5c130e26e7037", size = 1186719, upload-time = "2026-08-17T19:49:16.84Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e8/46de4400d5bf859f640feee85bd7e32235f68ddf25db53c63be78e581e3a/tiktoken-0.14.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:26cc4b4840fa0e9f4b72ed489883e12f57e00d1021ca794720e3c29a12f0edef", size = 1204660, upload-time = "2026-08-17T19:49:17.987Z" }, + { url = "https://files.pythonhosted.org/packages/29/ce/af8964c38bc8226dd8950305b7a255fa33345d5572f78af7275a313d28e0/tiktoken-0.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2fc834fbe3f6a0736905c36ab709537e6840dbd63b982dc9e0216ae7d305ba1a", size = 1250932, upload-time = "2026-08-17T19:49:19.28Z" }, + { url = "https://files.pythonhosted.org/packages/1d/4b/323631116fc986d9cc5bbeb2b8223c7c85e61a8bb94ea5ab4951023b149b/tiktoken-0.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ca4db6ff5c5bf600f9b7761a0070ed44dfe5797a76bd432fb978bc480ef40c58", size = 1315190, upload-time = "2026-08-17T19:49:20.467Z" }, + { url = "https://files.pythonhosted.org/packages/18/8b/ba48a73729c9270989b36f37ab2ed5525e52690d715097c9fa791aaa5d05/tiktoken-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7aab286a020660a039097912a088236b985d18a3090d73f136c4413d29d37ca0", size = 987717, upload-time = "2026-08-17T19:49:21.704Z" }, + { url = "https://files.pythonhosted.org/packages/1d/10/b73b7e319179e0f60b32475f783b044f9cece872c53b6662664e9084b0d0/tiktoken-0.14.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:14b47e3674f2624803a8acc8fb367b7e24fc53055f9df3296482fe9a3a34a232", size = 1096280, upload-time = "2026-08-17T19:49:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6b/09999a9bf1d559670d1680e8f8e419ac0e2c5f6aac82e9bfdf70f260b30a/tiktoken-0.14.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:19d643d701fdaa70e5b9c7f8f96abcaffe77ca5e482a3a1a7dde46feb4284695", size = 1040433, upload-time = "2026-08-17T19:49:23.998Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7b/8537be0836f3df99b2a636b44399bfa43cd757f2b8b4097dacb794cf24a7/tiktoken-0.14.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:e4ddf863b59347deaa92302dcd90e5eb003cdc9be06ec2b692c38d1bdd9efd49", size = 1186989, upload-time = "2026-08-17T19:49:25.021Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9d/f9c56d7a943a4468abf9ef37661bb9b8e0cd3aa8aa87368c7146cc3f3222/tiktoken-0.14.0-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:60c47ca69ddda0dea8256fffd12e1b86f4b59734a20e4a70c61f63cc5f021df4", size = 1204615, upload-time = "2026-08-17T19:49:26.37Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d2/98a38579db25c4a8a84e31dd95d9072ec5f21f7e70de591da0412e29b25b/tiktoken-0.14.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:728303a072163130c5b477b1f20d6211895569c1d5302c24ffc93a3009160871", size = 1251828, upload-time = "2026-08-17T19:49:27.423Z" }, + { url = "https://files.pythonhosted.org/packages/0c/83/467be424746c039c5493c0f4102feab16b9b48eb6f5c089b2a2438e3cde2/tiktoken-0.14.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3c5349c9f916283bba32bec8af69b763e4faa304dc004d0eaaea66a3cf004c1f", size = 1316260, upload-time = "2026-08-17T19:49:29.101Z" }, + { url = "https://files.pythonhosted.org/packages/02/ee/ddf46ca78e371f5890e96b6e7d089a85b3536432be219851eb0481786ca8/tiktoken-0.14.0-cp315-cp315-win_amd64.whl", hash = "sha256:1b6e4adcfd285c44502aed51df98aaaca4f0fea028165dbf8a9e857b9f98d8ea", size = 988230, upload-time = "2026-08-17T19:49:30.246Z" }, + { url = "https://files.pythonhosted.org/packages/2a/00/5162e90c851a28da18ed382d34898b79a8022548e5619a64e14c03ce7c3d/tiktoken-0.14.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:11d8211b290855d2721334ff17dd9b3a17bfb26872be01f25d73612ef7ece890", size = 1096186, upload-time = "2026-08-17T19:49:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/65/97/a5a7bfccf25b1bb65e82bae8edff11ac3c9c041c374b7b4a823d60c38133/tiktoken-0.14.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d0781223705199b289faa59601bb9c2441712d4c600dd13c43d8fd6a33d22cd5", size = 1039947, upload-time = "2026-08-17T19:49:32.848Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ba/ef427fc638f1439181c5e12dd26b70e881861f89c007aa7e5b36300f8342/tiktoken-0.14.0-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:2ea70afba6b9eddbf22c165142e5f0a2ad7aa36a452873c48b57bb2aeb8492ae", size = 1186997, upload-time = "2026-08-17T19:49:34.121Z" }, + { url = "https://files.pythonhosted.org/packages/3e/88/2f3f85a968cdc514152129af0a060ebcccb067005a2f29b0d5ef3c838514/tiktoken-0.14.0-cp315-cp315t-manylinux_2_28_x86_64.whl", hash = "sha256:78571efc311c30b73f31eb949a921d6dac39a5d9dc42d1cfa8f8db157b3447b1", size = 1205211, upload-time = "2026-08-17T19:49:35.284Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f6/80760e98a08e6649d2d68afb6035af713121dfb615acce8c4f73810ec438/tiktoken-0.14.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86f66c85e796f5d05d5c4a60ec1d40cbfebc47a32464053528c797163fa9ab89", size = 1251479, upload-time = "2026-08-17T19:49:36.419Z" }, + { url = "https://files.pythonhosted.org/packages/c5/84/50966fb6918a0fb9b32721277e5342bf729a2d74350074d662fbedf9772e/tiktoken-0.14.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:149d97453c4c98c04b081d64a85e635921269b532710d6faf81e9e82b790e7d3", size = 1316673, upload-time = "2026-08-17T19:49:37.756Z" }, + { url = "https://files.pythonhosted.org/packages/35/5e/9b01afd037bfa22a0033963fa091e0f75b6fb15cd85bffb42ff86e697323/tiktoken-0.14.0-cp315-cp315t-win_amd64.whl", hash = "sha256:561e7580f84a79859af1ef6f676968e9030fcc3fe195700b15235bca64f009c9", size = 987929, upload-time = "2026-08-17T19:49:38.947Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.23.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/7c/2cabb2174e772636683008f2c5621949b645da7d303c596589e84516a184/tokenizers-0.23.3.tar.gz", hash = "sha256:cded33237c77caeef62944d32aa9a7ef42bdce2b3497e18d137e072a8c4be438", size = 385286, upload-time = "2026-10-09T10:16:55.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/2e/4ce5b9716f26e526eff6b0502ebed4ea8d7161f03b3c77617c9f25528e97/tokenizers-0.23.3-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9d2b5c97daf61688c2ad1803ca851800feaba50fb68d5821779e9ea5880d968c", size = 3148800, upload-time = "2026-10-09T10:00:51.457Z" }, + { url = "https://files.pythonhosted.org/packages/b2/72/01e49f032bb346e5aaf06c10c74fe8aeec847173adbadd66eb7c53054bf2/tokenizers-0.23.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:68649e97d5b43c44c031d8d848874a6eecae8f8fe40ea989aa777a5a83aca716", size = 3101381, upload-time = "2026-10-09T10:00:54.063Z" }, + { url = "https://files.pythonhosted.org/packages/15/fc/ae987741829b1cd547668c4c94be732ae3eefd1d74344e64c3d2ca714acd/tokenizers-0.23.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec82e80e65a862275b97c3d90b7a523df8d9519ee48aeb4e9625b2cc909274e0", size = 3519944, upload-time = "2026-10-09T10:00:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/1c/da/cc8f6c030afaf05fbddc608158fbb761dca46913cbeba6b112e59fc82e2a/tokenizers-0.23.3-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64a0713180ff16829d4e7f39a658b77ea11443af4e1aa46523692943c9b1414", size = 3397695, upload-time = "2026-10-09T10:00:57.444Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f1/256f78d1365fa2cd3ea6db716883d74667c8cbb6a21f15fa5b89a773cdc2/tokenizers-0.23.3-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddedfd4b3b4be6be24ff6ca645c4a37fddfd305f6f3e354c54cf10b715c48215", size = 3753125, upload-time = "2026-10-09T10:01:00.165Z" }, + { url = "https://files.pythonhosted.org/packages/60/93/eee007ac2fcbf4ecfce7fbc354826cf3611f56bdb886f3e91b1f7dd06b8f/tokenizers-0.23.3-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a89614730d7b80940a5d2ed9320e1ec8add5a745c6151d8d05071b7215505b6", size = 4018598, upload-time = "2026-10-09T10:01:02.05Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f9/0c96c4739461fce9d8d865b416728081bf6230022d7163bd6244f35f4b31/tokenizers-0.23.3-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e88646b8580c5ad7f4361477f1298e9cc01771a1ee9aecfe32c47b8ff614cc38", size = 3602442, upload-time = "2026-10-09T10:01:03.77Z" }, + { url = "https://files.pythonhosted.org/packages/3a/40/6706b82693715581457c6d5423eaa7faae576bb0526c5738a57085eb4449/tokenizers-0.23.3-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:376851d22bcf9d650a5c3090bb83e6cf9e895fbf0595369fa4cd43c1f69b5f87", size = 3396193, upload-time = "2026-10-09T10:01:05.48Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0c/85946de40e25b7364b8f1bcf56def129069acd5bb364b7c86a32919e1a23/tokenizers-0.23.3-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:bf501c40b72d2d5c8623620210430e9cac1ce47a46e45b34107b70a1557d46b0", size = 3553483, upload-time = "2026-10-09T10:01:07.387Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6b/8d615d92cad1d511ca5ab188d1c7c167f0b3d295cc0d96207f9f82d486d8/tokenizers-0.23.3-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:114e2b55ed177179d59f4ab98200a4471e11e78f9e4b5a922d146740f96fcf52", size = 9972248, upload-time = "2026-10-09T10:01:09.437Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7d/a922e37ddd58d1b463bbc2ad08120c8f59c60b814cd353519a116b24f8ba/tokenizers-0.23.3-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:d3407fb7b9c4d75dd68850ffd7180bc0a5d2dbaf0762d888e612f31fec3f9c6b", size = 9802957, upload-time = "2026-10-09T10:01:11.869Z" }, + { url = "https://files.pythonhosted.org/packages/4b/06/5d3f506a86ae0699a0e4ea05c05978f9aee169ef2c1d844e68c971cf8194/tokenizers-0.23.3-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:84513ef0aeb8bf8f4ea11a2e8a7ac163ec5288aa115e649a59b470ac5c3107df", size = 10145487, upload-time = "2026-10-09T10:01:14.268Z" }, + { url = "https://files.pythonhosted.org/packages/26/e5/065625317690ea3548d834dad81f48ea1fd32e4964610e658e195d7fe28e/tokenizers-0.23.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e05ab7baf7f47b406a95fea6f3b0a484b2ddcd9e1d14b68844c457eb755085a3", size = 10266026, upload-time = "2026-10-09T10:16:33.054Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/babede85d0d19f5e3deeef0063e01848141329934d3d77c31b5cab5ac2b4/tokenizers-0.23.3-cp310-abi3-win32.whl", hash = "sha256:1ebf28794e7e4954e20a7f70fbea410b2d1f0418f7dbbca97ca384fcfef38c25", size = 2588086, upload-time = "2026-10-09T10:16:35.686Z" }, + { url = "https://files.pythonhosted.org/packages/d1/6c/24f074c9a0efb98e61b20aafe6b2641922d5db24e447d5d6daffd9e17555/tokenizers-0.23.3-cp310-abi3-win_amd64.whl", hash = "sha256:1f0823bb00c5fdc98e487354d54dd55a03848d61a1a0bf29a68c77f24f3b26c3", size = 2872101, upload-time = "2026-10-09T10:16:37.533Z" }, + { url = "https://files.pythonhosted.org/packages/53/77/a476b6f73a661c11d113a342d2326b91506cf2285f0995d1212a6bb2022d/tokenizers-0.23.3-cp310-abi3-win_arm64.whl", hash = "sha256:7e48734d2de9260d86f03ab056d2cfeeff3869f61dbd49aaa15a2793b5f3458b", size = 2742580, upload-time = "2026-10-09T10:16:39.244Z" }, + { url = "https://files.pythonhosted.org/packages/65/46/f66baaedd42414a3f583c47379dc350e3e1f858a690d2574fd85ae70681b/tokenizers-0.23.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:efa3d7318406b4d115dce61ad5061953f1f44b128e79c020ce4615d763e23b6e", size = 3154274, upload-time = "2026-10-09T10:16:40.876Z" }, + { url = "https://files.pythonhosted.org/packages/c6/41/8de8c63b2d935eee5a0f42011fb7b786ffafeab0b8eb6d17acb8af2293b7/tokenizers-0.23.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a4fbb3662f9f59d199d61338e54b4bcc11d07ebbb1aeb3540dacb2be9c521cb7", size = 3077805, upload-time = "2026-10-09T10:16:42.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/08/b1cbae8dc8fc7c91f992ac2d87a086e9b3f25a28814047ca16a82fe8c87b/tokenizers-0.23.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de536665495cb4b409d25bade41963f801aff4225c19a6b804b048f7d14e34c7", size = 3491678, upload-time = "2026-10-09T10:16:45.093Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0d/aac0cb2f3a1fdbef514145b4c5f2df4d05deeb1ee8f73ae641a1b4a62a85/tokenizers-0.23.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cc24bb457dd4a8af89c8fcb40074d570129ec473df2a866c276ee55db4749d7", size = 3367420, upload-time = "2026-10-09T10:16:47.112Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1d/41a697d0c193a320b243fbd68b2057b6eb2f01ecf80899e1a16e646ff699/tokenizers-0.23.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:acd5c57b4bd3e56e246e2731a3a3a6825a7a7d89b7e3b761ba80bc521710f04b", size = 9945973, upload-time = "2026-10-09T10:16:49.326Z" }, + { url = "https://files.pythonhosted.org/packages/37/e9/b56e619fcd583000a2b1254bb46af8dc6a174d3ba3329f454ad5a95a2be2/tokenizers-0.23.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82eb480f6f1c21cea3349dec32cf1a6384c6c1e775f00f83b0d51197bc013687", size = 10237491, upload-time = "2026-10-09T10:16:51.943Z" }, + { url = "https://files.pythonhosted.org/packages/6f/68/f58b3beb95f3b62816e91e5e768e684cd63e58f9cbece22036dae3b1c971/tokenizers-0.23.3-cp314-cp314t-win_amd64.whl", hash = "sha256:1554a6eed34d9d6a78d23360f4e06df8dffab1ae08c7e8488e0b3e3b36cc266f", size = 2847654, upload-time = "2026-10-09T10:16:54.166Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] [[package]] name = "tomli" version = "2.4.1" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f" } -wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396" }, - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/tomli/2.4.1/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe" }, +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tqdm" +version = "4.70.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz", hash = "sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4", size = 171846, upload-time = "2026-09-11T07:25:16.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199, upload-time = "2026-09-11T07:25:14.599Z" }, +] + +[[package]] +name = "typer" +version = "0.27.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/51/d33db42cc72ffd8c30777547b42d01f0cbf9d95a770457698d0174b3ed71/typer-0.27.3.tar.gz", hash = "sha256:d0396f770a560ab1b0a8504e13b5f254b728cedb05c61cf0359e944e50ce8901", size = 205303, upload-time = "2026-10-06T17:24:16.61Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/ea/2e31b67051e91a133189e9c000c222502ddc6969856416de0d095de4c0b0/typer-0.27.3-py3-none-any.whl", hash = "sha256:e50022f28b82a86313e54501317a1db64bf8f8d036ff8cfe5ca7e47675454aff", size = 123312, upload-time = "2026-10-06T17:24:15.054Z" }, ] [[package]] name = "typing-extensions" version = "4.16.0" -source = { registry = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/simple/" } -sdist = { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/typing-extensions/4.16.0/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5" } +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 = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/34/30e9280707135d2cfc589dfff3cb796bd07a3aeb1a3e415ba09dd89d7bb4/uvicorn-0.54.0.tar.gz", hash = "sha256:a2e33cbfaa0306f8e6b0c13e0cb89d7d7a2da3e62b90c66e18c33d9807b28620", size = 112283, upload-time = "2026-09-25T06:52:37.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0c/b54a4fdd7f90a3af8b02ebc9ce6712c2c208b7926a2f7bad95c33ebbe943/uvicorn-0.54.0-py3-none-any.whl", hash = "sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf", size = 87427, upload-time = "2026-09-25T06:52:35.829Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "yarl" +version = "1.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/16/e8be8e2fb175bbf41a0680381a319f1199fae256588241a2ac8677eafb49/yarl-1.25.1.tar.gz", hash = "sha256:03dd38de09bc213e9a8b29761eec33ee1d5318dac0e49d8af36e4d27830e23a7", size = 246245, upload-time = "2026-09-15T19:35:02.264Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/92/55fa9ee84cb8ec9930a910b5753936a926f918d8cc8965bdac571479c095/yarl-1.25.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:142c06c4d6a35ee3ec5da08499805e879cb3ca7c1fbfbecb0140fe72403818d6", size = 144695, upload-time = "2026-09-15T19:29:53.114Z" }, + { url = "https://files.pythonhosted.org/packages/16/b4/9edc8e605b16b2eb5bd0c2ccc73e2b15aab583862460767fb43f91f11839/yarl-1.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:24ce942011a61953e7d313438038f4d32ff21387b775f58a957f7a07dd55ef95", size = 104332, upload-time = "2026-09-15T19:29:55.443Z" }, + { url = "https://files.pythonhosted.org/packages/ae/de/4204e6646e278b1cd4a9cf73ebe8b7cfcf16693f1249b324eb273d67602a/yarl-1.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e23c82b63cd7652fc24d33ed6cc17099d607aa3b4fc4ddc75e95062f3d82df4", size = 104449, upload-time = "2026-09-15T19:29:57.239Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/21d74789198ad68a23a209ab0d73fe5a52d39ebbd9945041e72827acdd77/yarl-1.25.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee202350cf57abf0e9502a41601841019c25d3db7ff52d980aaf31446254059", size = 116828, upload-time = "2026-09-15T19:29:59.031Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/90891ddb61848c067ebd1fb505ab5902a6e7d4707c2f3823f67ff37883d3/yarl-1.25.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5df89f769cc8ff94c3d7e7603386fba309d25ce5240132d26c15baa8d0e96c4c", size = 107150, upload-time = "2026-09-15T19:30:00.868Z" }, + { url = "https://files.pythonhosted.org/packages/a1/15/f3d18743d3688b5f01aa83034b6c00665692ab20e1d0c89a29000d4979ea/yarl-1.25.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:83e9f4a25085bd4b7214701a0794ff1f50fc633ffb8bdfebf07abdd81c2db126", size = 124704, upload-time = "2026-09-15T19:30:03.128Z" }, + { url = "https://files.pythonhosted.org/packages/99/37/718789d8004775d6a2db86b2afc72b1d156e0590b88a1b8634ddd4bdb8ee/yarl-1.25.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e636b64d24fd9c38053c5e389a1174c66361fa49dcfd220f4dd35b4abde7cb89", size = 129246, upload-time = "2026-09-15T19:30:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/28/b9/7818ac6dec7fbcd16be2d19495807c01e2c38834e57d41c63356a75e786b/yarl-1.25.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5637ca8d0bd7fb72648a6c7934af4baaccb697657f7438c9d264fc2abb8b0b1", size = 118071, upload-time = "2026-09-15T19:30:06.977Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e3/8d098cafb30a64df7283b5a5dd0d32d22a71b18a1b9ec071d0776ffe5c2a/yarl-1.25.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:683e362b8ba453080f7489c66f4ea794e751c35b72e7eab3575ef784c2fbc7fb", size = 116180, upload-time = "2026-09-15T19:30:08.836Z" }, + { url = "https://files.pythonhosted.org/packages/20/9c/fbe9432478d87e00eef80242e15179639df162cb3d5d8d978fc56dcb6c51/yarl-1.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:df23df54b5114a17c2d0ef192433e2e5a9f0c5178c32375e90b7cfc965f349d0", size = 116578, upload-time = "2026-09-15T19:30:10.782Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8e/b3dfd03732236b86b0bcf06a72a37de412ff6f82e719a947c3c651d4dece/yarl-1.25.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f53dcd26694f148f738edc052b5a69234833e739f10f4c3287bdfd8ec0f7b326", size = 108886, upload-time = "2026-09-15T19:30:12.951Z" }, + { url = "https://files.pythonhosted.org/packages/83/e1/94dacb650d4963d5f844bc3df7ba70288cb7b68ed5a4d070da71acb19b14/yarl-1.25.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b8075fe90bc08e40b8b8a1874fab42ee4c7b56af05c5886e9cc841397f916908", size = 124098, upload-time = "2026-09-15T19:30:14.963Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3f/c93f76a218258c7bfc60dd27fd56e9102bdbc625517f4a6ee673295fe4e4/yarl-1.25.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a8c2b841478068440d8b733005d13a5ef535b9928cbc05f17182d410f32ba449", size = 115768, upload-time = "2026-09-15T19:30:16.736Z" }, + { url = "https://files.pythonhosted.org/packages/a8/79/4d93f13c3b05cda3c962805dec28cbc255c50239b3457808abc5633a00c2/yarl-1.25.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ca32926d7d77bcc8838425c4c95e040a3ace1cb7dfdae599013458dcda2607ca", size = 122461, upload-time = "2026-09-15T19:30:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8e/e2ba83b3a9bfc1d3b882ad35fa8abc04a7af4d9356f2e329a23d0c8d889d/yarl-1.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:192a866877a49993949ef1975864ad8728bea28ee810f6abe1a0729c2b500426", size = 118295, upload-time = "2026-09-15T19:30:21.07Z" }, + { url = "https://files.pythonhosted.org/packages/42/67/cb5ea1baa0c0ac60bda44ce59f601133154af2a2e67722d3517f8793855d/yarl-1.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3f4d48a6112712973e676bd792121fee470e432d749177162d9949d5c9460a1b", size = 102929, upload-time = "2026-09-15T19:30:23.122Z" }, + { url = "https://files.pythonhosted.org/packages/2f/85/9a1e98de10fc0e738d517efd2a9984c3e7db6cf35fa72b2b373988a9e9b7/yarl-1.25.1-cp310-cp310-win_arm64.whl", hash = "sha256:48796ea00a303961507dc6c8437c4b325a6fc3f95f7c36c71b91ea9a8150963c", size = 98854, upload-time = "2026-09-15T19:30:25.102Z" }, + { url = "https://files.pythonhosted.org/packages/83/b3/2cea721d495ca57f8f414aa4867ee263f486b274e07463158cf52f02ba7f/yarl-1.25.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d693bf4bf534e9ba3ae2780cfd577f5135629f7b5ac653490859d0b77864865", size = 143794, upload-time = "2026-09-15T19:30:26.946Z" }, + { url = "https://files.pythonhosted.org/packages/55/e6/cd145cff8e5cf60b8b3c41fbecfa2a45028a8dec3fbc52bec03595ff3d3b/yarl-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ab2054c5531af2a9ba7b69b8ec91e4f884420e83a8c5e579b013084cb57e5e5d", size = 103993, upload-time = "2026-09-15T19:30:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/ce/7c/fbb40fe2d53747c40aa36a9e2bd2178a202f942bda0b670f3306d4aefbce/yarl-1.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:564fdc7085d2245ab84f88882fdb1d6ac0723124bff6ded35bfb1c00f812630d", size = 104010, upload-time = "2026-09-15T19:30:31.069Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0b/5a516f70641092283f57cf3670bdb75e7327bcc0dcb5038697e4dfbfd569/yarl-1.25.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acae6b45d1ace09b6ba3876da43b88366ef368f73b988c7f57e14231753d4420", size = 116444, upload-time = "2026-09-15T19:30:32.894Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/d1a627f827b0a404ae0f5647cab0534959081cde13b0756a783469fb3b5e/yarl-1.25.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1fb2a01ba8cd9c5d2c5dc1ec35e0fc951d04b4f037541d4ac090c993ce58b3d7", size = 107565, upload-time = "2026-09-15T19:30:35.188Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cf/c8e0aaec886840a6c4480fe44eaec7cd4319f79a563b79321473be79c56f/yarl-1.25.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e92b6bcc741b86d67606c40d3cb9c7cc8e6c737f81e31f4a94efc204456c92e3", size = 125006, upload-time = "2026-09-15T19:30:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/50/26/0cce366d54a93cdc8342965dc7663385e4db161625cc1e8b18e786753d24/yarl-1.25.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72c34ac7ad4314c19362d5ce27626dcc8429bd30bbf8c179f4234078851f9492", size = 128717, upload-time = "2026-09-15T19:30:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/95/0c/a71501bbc1a674ff72c4d6c2b75f4d9a5af819f5244c3a7558080a8802c5/yarl-1.25.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5add7b4ca7afeea91d52e4d4e4db3b1fe9885b71f07054560d8c4296b7441a2", size = 117728, upload-time = "2026-09-15T19:30:40.809Z" }, + { url = "https://files.pythonhosted.org/packages/e7/6f/c3267ca01defeed9ed9c4ff9b17bd54915432c405945233265b707475d1c/yarl-1.25.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:def538065f9e4d4cf1ae164bd59aba00dfa84f03923e0de4c3788f252d6bcd17", size = 116224, upload-time = "2026-09-15T19:30:42.818Z" }, + { url = "https://files.pythonhosted.org/packages/8f/69/fad57ee52d648431718ee0f1f68966a99c1352c3924688ffbdcc9d3fe51a/yarl-1.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a191bfdb30a79b98e5d175d75285f9fcb78bf0e46ba5efda042e1c72071a0de", size = 116299, upload-time = "2026-09-15T19:30:44.966Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d4/6a8c1e29f33338687ca278cba0a8fbf6525a322c2c02a9a500ccbe041152/yarl-1.25.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:71f42c5b9a948c113bbdebfa544598321431d064ff959d32e99b1feb61d68345", size = 108625, upload-time = "2026-09-15T19:30:46.874Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d2/3a35ae791c9cb6522c106923ff25c3230d999091e5e65511bca23bbd9914/yarl-1.25.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:72849d892954be4d09e569b8b831ac39ce58417fedc767d4308a0fe542018a40", size = 124515, upload-time = "2026-09-15T19:30:49.082Z" }, + { url = "https://files.pythonhosted.org/packages/be/fd/2b022109a6b4af0f7dc371cf7500af380b0d4f034010243e1b0ce218dc93/yarl-1.25.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:efb01a106f971cb3752856bca2318bbdf7f01bd8823779c461586cbe5ffd5258", size = 115711, upload-time = "2026-09-15T19:30:51.208Z" }, + { url = "https://files.pythonhosted.org/packages/18/59/f7586271136c3ddb0126bbfe661844699369b76b555fe38c4efe86870b2e/yarl-1.25.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a1daf47cd95a7c3a63456336bc5aaa8c86dd3a47d07ed3d0e76132ae4666a5a1", size = 122751, upload-time = "2026-09-15T19:30:53.535Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0b/07f7a2d881f7e16c385b47fc1753382600847cad305dcc7fa0c25828acf8/yarl-1.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9489e6abf47ba37f332075a91444c7cfedb03e6ce99fbb2f116bfe1ce810da3b", size = 117983, upload-time = "2026-09-15T19:30:55.277Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9d/8cdceec66a9b940700cb45931741403f045b162afd79bcb93c41cadd0972/yarl-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7306dee25b8a0e737363f347362b875094b4dc4e367311470656ae420fdbf8e", size = 102894, upload-time = "2026-09-15T19:30:57.606Z" }, + { url = "https://files.pythonhosted.org/packages/17/f1/7ec357db1d3ad2863542d71e8fe64a126bbec17b134cd7d30951196809a5/yarl-1.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:abb1384477f5901d436b5d2e5465954de46ea6098f59163d243660b5c4461d35", size = 98609, upload-time = "2026-09-15T19:30:59.705Z" }, + { url = "https://files.pythonhosted.org/packages/75/b3/cd32ac66ae622b854c2df0ac52106dda220d361b65a64fde7d5b3684aa3f/yarl-1.25.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d7aa6debf92a1dd14cb5280b083a764169a13cfb23a452111160274ed989f4", size = 144798, upload-time = "2026-09-15T19:31:01.821Z" }, + { url = "https://files.pythonhosted.org/packages/61/fb/a2c52a8007c2051ba74662afb112ecf3d00346af4c25e33df9d80fd14fb8/yarl-1.25.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:83d4a37e4b95da4d8bda930d6d35b75b4cdadbacbb4980cae290ea3100b5d51d", size = 104583, upload-time = "2026-09-15T19:31:04.05Z" }, + { url = "https://files.pythonhosted.org/packages/be/dd/ee38aec8e09fdf957e50d4085453fbe202f56c6c3b4cf07b81cdb4f09ee9/yarl-1.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e029648f9c951db30e98a7d7ec90835db88ec4b32820efe2a9bdc2287e032eb6", size = 104325, upload-time = "2026-09-15T19:31:06.338Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b3/058dbfb1857b484c9cf9cc135659f50b85ce66e03c99e44dc2f7b6161f55/yarl-1.25.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d781294bb815ecb5ea57ff6bbf8038e0a31a95fdf3e1788f66e0dc100d64b58", size = 115358, upload-time = "2026-09-15T19:31:08.593Z" }, + { url = "https://files.pythonhosted.org/packages/db/39/29693446cf0cf6b15a0e2f75a5d40f93c56819b05b0622196f45e95b5cc0/yarl-1.25.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e12c538e00e7c1b286a07061046b90e8124e6a9793efae2c70db6a4aad07faad", size = 107658, upload-time = "2026-09-15T19:31:10.802Z" }, + { url = "https://files.pythonhosted.org/packages/86/b3/3c4dd7e1af43b931fba95e0a722737f2ea94a6d199c802585282831d7abd/yarl-1.25.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e4de3ac4adbad3d0bc7c6f4360a7dbff5de2f15e3b723be3198074e17fd9c40", size = 122660, upload-time = "2026-09-15T19:31:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b5/1b60dbc3cfc9c5712b15148c206748f2bc93953ffdbe25ea75b63dfc89c9/yarl-1.25.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:419f392a1da624877975709e3864dfe833af6cc7671b39318086d456e288380c", size = 126506, upload-time = "2026-09-15T19:31:15.088Z" }, + { url = "https://files.pythonhosted.org/packages/bc/7b/ca212cbe170ac8b96e45317ecbcf9c3c3ecf0cdec98d5b088a9c4088929b/yarl-1.25.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6f117789d22dce188e5754e8bc65b7e6ebf8cb73963b9fa761f672a5883769d", size = 117050, upload-time = "2026-09-15T19:31:17.241Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c3/72b4938cdbe619ad71ac156182faef4908846b84dc3ca4dbb4c4e6f84014/yarl-1.25.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80e47012e730da131c9f059c80936783f9659aae22dc31c03c0595590d11ed54", size = 114174, upload-time = "2026-09-15T19:31:19.294Z" }, + { url = "https://files.pythonhosted.org/packages/e8/43/268717870f9ba0cc9701a95181587f6dc8c5f387aab4aeecc83158f38a79/yarl-1.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e80f557716fd765439577131e526b8942ffc2c07bdbc5e39fa62f660ba1e963f", size = 114944, upload-time = "2026-09-15T19:31:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/da/84/baa5bf504d51fe062c4bcaf62936da97fffb43285978d0b39984824231fd/yarl-1.25.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f61964f235a43738bfac50da46fc4254943a7eea3051aeb0b6fc7c992c29fadc", size = 108263, upload-time = "2026-09-15T19:31:23.388Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/779a2ed9e0152a601a27039bed9aead3f0b79797a67e2c44bfa444622dd8/yarl-1.25.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e546fe1d4a93ebc2910f0d768baff19faa09843ab3f2036a67ed6e69fae4419d", size = 122184, upload-time = "2026-09-15T19:31:25.343Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1f/118e9e5b8f07694d63fd3222e801d7782270003f1a222aa798df3f8d5933/yarl-1.25.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cce0727fd5ac04d372fa9bbfde9febc2bcf209aadfcf0468e45dec72719895d1", size = 114001, upload-time = "2026-09-15T19:31:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/a4cf1cf372313734b17996d4007f9f73596e7a178b9485802e5494ecf484/yarl-1.25.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af4ea5b37403ef4e30f3927eaed540db942bde01d8d3ff083527c0704d1c9c68", size = 120565, upload-time = "2026-09-15T19:31:29.47Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ad94f93ca731bc9e44d321833ab96b82a4f9f5f63cf773f81a4aeea5ecc1/yarl-1.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:68782fdb4027b8d1eee25ec35e9a6db05e863b899eb0310b3a33b6c3fef55707", size = 117060, upload-time = "2026-09-15T19:31:31.367Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/51a7b4abf4ac593b8e7eb3794b28e5a35ae26eed8bc04787628d215af82f/yarl-1.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:7d575b54cb3863ef9bc290ea4b009999d55dc237326131e4853cf33e888fee03", size = 102593, upload-time = "2026-09-15T19:31:33.329Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/0941a6b93a58b59a1ec75e5333bf06929b671309c43c0cd201c172d9c39f/yarl-1.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:bc3ac7bf569f6b64dad04dd7808c7872dae8a97df657856eac05e9b7e3614a85", size = 97697, upload-time = "2026-09-15T19:31:35.855Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/2f3129bbcc9a5c8ba12cc2b29d8060a3bab9c8043c456cfd4b5ca3188890/yarl-1.25.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:25868beca8b6765f8f7d0e11fe6dd7c66dd4b0793b9500286d20cc92352126a5", size = 143623, upload-time = "2026-09-15T19:31:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f1bc3390fdca352826676b531d0712736f156919090206700421d46b2c37/yarl-1.25.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:10b2fd95332f0d716d5eee3c9fb2ce8eada19082de7fee83d32e37992fd75c26", size = 104011, upload-time = "2026-09-15T19:31:40.25Z" }, + { url = "https://files.pythonhosted.org/packages/a8/aa/50acc5c3e5da04172ae3c281c75405af4d2ca911e16120ab0563f4dffb66/yarl-1.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f12afda4eea8c8994a76d4df1875c765194f5fbe8a9d197929ea303caee29ec", size = 103677, upload-time = "2026-09-15T19:31:42.46Z" }, + { url = "https://files.pythonhosted.org/packages/30/d2/7d1e0ab9f8390e1fbcede5a6dbf70d23c96ad09b8c5567f3a514d1ddb0e2/yarl-1.25.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14b79a30a93a3ce2e8832603fd0ab780ada281b0ba5110b519a634f2d7d7d1fc", size = 115392, upload-time = "2026-09-15T19:31:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/71/e1/5ba1e3a2a22139213655e760919038e8ed7e2d4a99826d0bbddb3beb96e5/yarl-1.25.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bd6340d20ae2c7ca719b87b426e808e90743b676d05d4c26c4fb5ca71f41184", size = 107493, upload-time = "2026-09-15T19:31:46.273Z" }, + { url = "https://files.pythonhosted.org/packages/f5/53/780653d5e0f73831f467cf13548912e5eec97f21dc49fc8daf21da027df4/yarl-1.25.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:126a2533570c554719ca40a1288fdee1700b6bc82e7131aa69fa85252d92e651", size = 122537, upload-time = "2026-09-15T19:31:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/03/92/d54fa70236c6036271c9c9c09fd978df5cbe3ef49ef6c46e9b833476d215/yarl-1.25.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a3faadac7d812ddac258feb57b9846b60c1b437c4f4b9ad42595c6f6fe4390df", size = 126170, upload-time = "2026-09-15T19:31:50.872Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b7/a82a49bf88340b837ef6972b508a1604ae377b9e6904b46b10cf5f1cf925/yarl-1.25.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be80550d9bfe83d9b62398a37081a90434e6df2d978ec345c3d2820de6beddab", size = 117012, upload-time = "2026-09-15T19:31:53.189Z" }, + { url = "https://files.pythonhosted.org/packages/ef/78/5d684b411e3f3602464ee9b538db48205038f8605872985f61efb809ced0/yarl-1.25.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e07595c7d6f4db270ceede356a1bd1c07a34f1c26f958d1ed0cd7b48e0d2bba3", size = 114950, upload-time = "2026-09-15T19:31:55.694Z" }, + { url = "https://files.pythonhosted.org/packages/2f/11/51d82b852c64f7fad0fc7a7ff3031517204887e874c722bbca839c0b23ac/yarl-1.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb96ed1ae6c7d072d60840c0434aef07a2df611812810807fbc54263a6053e9a", size = 115428, upload-time = "2026-09-15T19:31:57.966Z" }, + { url = "https://files.pythonhosted.org/packages/e4/49/9d1978049bf646b9ea918313926453c6901b71c92f097467777d47d36a88/yarl-1.25.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3feb99222553a8cbedfa52c2f59dd84c3f50d5b582c728d522caf8d72769a54b", size = 108428, upload-time = "2026-09-15T19:32:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/43/35/7b8f1ebb45d7ec3dda7d1909bf44f458de41ef91e2937f107733582a5166/yarl-1.25.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a2ed0ba415ccdf08f14bf544cb78346d0f76086707ffee24921a2c84dbf1305a", size = 121961, upload-time = "2026-09-15T19:32:02.436Z" }, + { url = "https://files.pythonhosted.org/packages/63/d6/d8b689ab7ca26edeb85f6ff28812aac7a25376eefc1780e303a7bfbaceff/yarl-1.25.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b49375d22299b0a834c2bca72f39aaecc270d96fb24c30424899676f487b22a", size = 114961, upload-time = "2026-09-15T19:32:04.456Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d5/1a1798ea4dc6b7ee3260010a27907ebc697c95dae99817d817ed446d24aa/yarl-1.25.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ef74070ac553c59eb4f04258722066d6c6135b7baa03b2e9f2da65c096e96d98", size = 120036, upload-time = "2026-09-15T19:32:06.5Z" }, + { url = "https://files.pythonhosted.org/packages/91/8d/b1b35ed7903da6669b1d367cb2c09436acd4ff508029b4f39a0c0c2058fc/yarl-1.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0a66db89ea473abeac4b70523cafd94db3772380e565f9d28af7a179b7af71fa", size = 117276, upload-time = "2026-09-15T19:32:09.401Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8f/4db01cef62caff0d7a4593ed694fb8a41a27a11158cab80d290221f13e57/yarl-1.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:1f51020b2eb8a003c84925638ec63c21a750a4bddd3a22ec8eac6a742dadf1b9", size = 101945, upload-time = "2026-09-15T19:32:11.545Z" }, + { url = "https://files.pythonhosted.org/packages/c0/5e/3ce00497c5c0babb74d4130c10c3828ccd215b4819d12020c42429f991ac/yarl-1.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:b10dd0557ba422715b5206b3743192135a6022acca8baec51aa127d0a75db8fe", size = 97270, upload-time = "2026-09-15T19:32:14.127Z" }, + { url = "https://files.pythonhosted.org/packages/80/cf/54023edfab7aa773b860503db0c56e962ccab0922803ee97988c176ea090/yarl-1.25.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a9ca696eb02e5c02a8afd872ada510eba9b7fe6e68b9572c2e9a9b1941e31e2e", size = 143975, upload-time = "2026-09-15T19:32:16.416Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a8/e6c1be0e6761d0f2d10bbf33a3e1e02b99dc83874d92945d7b461a72481e/yarl-1.25.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a5877f2255aab518ebe528289037699201d5dc5f045f2396cb30aa02db22f57f", size = 104018, upload-time = "2026-09-15T19:32:18.364Z" }, + { url = "https://files.pythonhosted.org/packages/6e/bb/dda344765ffd3430afe1a1c66c866a57fae67786537d4f14607df6505ac1/yarl-1.25.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7a5c3115595995779ee21f2567035793911c3802a43c74f3fbb0314929ec67ac", size = 104156, upload-time = "2026-09-15T19:32:20.459Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5f/ed1538bcd06009fe990d6d283dd7667f639e62a81e35c6d8c6ef6c08fb3c/yarl-1.25.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77e5099b99b37f3cf79c246998ca9f7313a78054cd1809ec46bc1afad47e1c4c", size = 116025, upload-time = "2026-09-15T19:32:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/a2/af/2185daf56b99830d3356ecfada46faaa49945de6626e842b7728088d4980/yarl-1.25.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6efaf45df6a849cef613a03a94c845647456662f85438c886bb67a9c027c8c2c", size = 106985, upload-time = "2026-09-15T19:32:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/c1/65/bc1ae564fb4b04a30b6a8f250e787772581c57e4c3d5cf07ac3359de3103/yarl-1.25.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5f90e44653c4e0f78501ed9bb7d3fce835a8d62b7c6ed0cb16557534087e743", size = 123030, upload-time = "2026-09-15T19:32:27.084Z" }, + { url = "https://files.pythonhosted.org/packages/6a/3e/e2afcde10d74e53b3fa889960991efb3019beda2b1682a01de720a302056/yarl-1.25.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:632da579b2d879f6bad20f2cfa35ded1efe2f4f77f8abb26a6234a5b236acd2f", size = 126765, upload-time = "2026-09-15T19:32:29.332Z" }, + { url = "https://files.pythonhosted.org/packages/a2/be/415b00c0fe5a0615b062a456b26623d7ec91c2bee20faea1a14045aa0469/yarl-1.25.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30eec96e8a91bd588ce897c9543f6d5d8d34b28fbcba28a4dedf20ebeae9fe57", size = 117199, upload-time = "2026-09-15T19:32:31.49Z" }, + { url = "https://files.pythonhosted.org/packages/97/27/3d8c63ddd3e8bcfd033748ab93876678ce59bacd66e4cb1ed851c9c5b37e/yarl-1.25.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:12b6bc4906e11f5e1a1cdcb12296e7afbd366c783cc8073403cd2fb74334e453", size = 115187, upload-time = "2026-09-15T19:32:34.137Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/7a81d0be1a502a26a0d4326c6f2ecb736c824f570ea1c6529f2b0b227b50/yarl-1.25.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9d6ed3d17bccce4c05343e1ca8da13bc5c02c812a4e7282ddd05e8769322d3fc", size = 116085, upload-time = "2026-09-15T19:32:36.438Z" }, + { url = "https://files.pythonhosted.org/packages/f0/69/39fff459916aa0fab42215dc47b759586fd80f94aa56dfc4a7c15ba6e0dc/yarl-1.25.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:f38a70074041d3b7e138e452799f5174198bae5bd5ab2000917badf403908c5f", size = 107996, upload-time = "2026-09-15T19:32:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/c0/39/80b9a55a3335590451d9ecf3eb593a8c635351f4c905ef056d7e8a8fd9e7/yarl-1.25.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca89e4e21854ed27ec753297dde84b16c9f8e53b14a4866fb44457d643c19f8", size = 122549, upload-time = "2026-09-15T19:32:41.151Z" }, + { url = "https://files.pythonhosted.org/packages/42/7d/a179c6757818bb59372a4adafd09f7f26a3b4a0f04c3ae404b544c0b0c82/yarl-1.25.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1ab7618921a93767387a4b83776f751588f5b5ae9bb5bc96620e2e2e00bca868", size = 115107, upload-time = "2026-09-15T19:32:43.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/2b/a773ac867e4ab53a98ed98e5cefe3bae31e6f550252ca9d1de266f1a40c5/yarl-1.25.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0ae12ff2b805fa02c4dab838005caef735e39986322698c48588d3beacb65c62", size = 120666, upload-time = "2026-09-15T19:32:45.061Z" }, + { url = "https://files.pythonhosted.org/packages/bc/41/52be6505e85b0f76b4f85b01b5de7e06a0512201abc2c95e14e099549174/yarl-1.25.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90c30ed53546da833c700115c0064c22120d1b1560f474699fd31f22dd668233", size = 117505, upload-time = "2026-09-15T19:32:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/f5/01/349c0386caedbbe488d519f252df54efac8a1459282d466c474bdd84a620/yarl-1.25.1-cp314-cp314-win_amd64.whl", hash = "sha256:acfa7e22aa6c6e7a5996a41d275bfa01efa7ea56ab890590280e9063e2cf5c1b", size = 103446, upload-time = "2026-09-15T19:32:49.615Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f0/8ec63180f77912f0dc4e5a42760cb8c08d20da1d5ace3578a01b84d1f3d8/yarl-1.25.1-cp314-cp314-win_arm64.whl", hash = "sha256:8e7d98cdbb6d71e726f7d525952867096053d1f290dd4e3c50d7d313a136f414", size = 99159, upload-time = "2026-09-15T19:32:51.686Z" }, + { url = "https://files.pythonhosted.org/packages/47/7d/92d2220d6886b70ab1ed8579533ac2af2dfac716d5d929001daff7986df9/yarl-1.25.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d21f0fa80a02d05299207eeaafef345d812ace96d5306e4ef265e1d419a615fa", size = 150071, upload-time = "2026-09-15T19:32:53.911Z" }, + { url = "https://files.pythonhosted.org/packages/64/fc/b245e448124bcda9340df38e3553fa222b50260fca027a84095e9bd8642d/yarl-1.25.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17c9877a89fb6e2bca6f9087eb24cd7fb434653946ef5075e470d23d49b52287", size = 106780, upload-time = "2026-09-15T19:32:56.443Z" }, + { url = "https://files.pythonhosted.org/packages/51/e2/9a6ce2e334ebf218a30335ae76fb1696459430d42f733b8cb0d7d65b84d3/yarl-1.25.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:29273edf1530e397bd07cb784db1fbe0d2590b77569f2e24679a9c0a2d763b94", size = 107361, upload-time = "2026-09-15T19:32:58.827Z" }, + { url = "https://files.pythonhosted.org/packages/ed/70/66e8c76b569b450d16e190f15071c916c3df70b0e33927e415ac497cf0c2/yarl-1.25.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7abffdf37af1cec6a2ad69b827aa84320db5894791bc8ed932dc93fb274b7e9", size = 114396, upload-time = "2026-09-15T19:33:02.24Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/0d82838a05c57fdc05bc8b66e8c92dcc0df15e27463a5f163142d521c682/yarl-1.25.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2239a02249d9326655419e0168a28ca9008938eaab31dc29fc875c217927a6c0", size = 104882, upload-time = "2026-09-15T19:33:04.494Z" }, + { url = "https://files.pythonhosted.org/packages/86/d4/ea08615c4edaa6049a13a2f1128944d068d1893abda7d708d4d7ea01599a/yarl-1.25.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:664ec6a520b74a1df2810666eb67695fcb77fa663e6ea0a25aaf2e529cb24dfa", size = 119485, upload-time = "2026-09-15T19:33:06.583Z" }, + { url = "https://files.pythonhosted.org/packages/1a/82/0898bdce9b1ae403b308b9c733d0d24af4a3464270c2c081f457b16c3e0d/yarl-1.25.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f1c91f5a5980a937ff8e238e98e6897e1ad74a4b1e2c0d68c73b5ffbb3f5c0b", size = 122490, upload-time = "2026-09-15T19:33:08.653Z" }, + { url = "https://files.pythonhosted.org/packages/d1/38/97d79b81c342b78246cfedb74809e68841f3198d21653e10d3232bd9c622/yarl-1.25.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c88edaec8c349ad4c5ad4c486a3defcc4b80ceb2f074436ffa0a87caf5e76a6", size = 115336, upload-time = "2026-09-15T19:33:11.056Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/2577896554cd310dc470adb6da0b7dd0b435cb63e2565204a7ac240e504c/yarl-1.25.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:35dcbea443fafb3eece757ad4e514560ddeb6c34cfae1582c620d7b293d7feee", size = 111825, upload-time = "2026-09-15T19:33:13.204Z" }, + { url = "https://files.pythonhosted.org/packages/29/6b/7ac49d8ba84a5c4bd73415a4c949d22c749cb3762579b3d50e48019a78aa/yarl-1.25.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:882569ff613758cac762a457a5d72d6e211b28d4bcfea89d1d71ea942b02eac0", size = 114655, upload-time = "2026-09-15T19:33:15.553Z" }, + { url = "https://files.pythonhosted.org/packages/e5/18/e5942a16723f5b72f9b1297fd5a85a54f6300cd15c0dcb5005b90cd89156/yarl-1.25.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d0f1489233a254bb3643d2f05de7d59019254d81daeca6b9162fe9edef57e0c7", size = 106395, upload-time = "2026-09-15T19:33:17.599Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2d/549fa46240781513ebc47ae7eb418df428a163a2a3d644cc9cbb3ecb7846/yarl-1.25.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f41753a76f4f63927d03a0d8ba8f5ce0f2083bec29a8cfaccc55371b1564b96b", size = 119277, upload-time = "2026-09-15T19:33:19.973Z" }, + { url = "https://files.pythonhosted.org/packages/76/16/4763f78dcdc0b3b9fb3842b04afe72b9320857c6a69300c62a0eab03d119/yarl-1.25.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fb0eb4955adf0579001581f2f71a126e8781ba61bcd120f127b0401163c6c2d", size = 112504, upload-time = "2026-09-15T19:33:22.464Z" }, + { url = "https://files.pythonhosted.org/packages/ae/b4/974e3edfe0d188393ce1cb9de400111c63fe61f4eb3b772a500d84c970d1/yarl-1.25.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a1e32763e641a1566507d90a8d3b19bfc3cc04a9d4e5ae3e32189874ed4b58a3", size = 116243, upload-time = "2026-09-15T19:33:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/b0/aa/157b940428da80c104ca09666a740e51c94963df65d5b112e06b52e4d7a8/yarl-1.25.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65b5b2066651b7432d389e9799d979c703bcc6ef44266bb8153ef54e91e4aab3", size = 115822, upload-time = "2026-09-15T19:33:26.886Z" }, + { url = "https://files.pythonhosted.org/packages/7e/af/19fbdce41412e1b96825544cc52cd7029d3724655d0988237972f078bd29/yarl-1.25.1-cp314-cp314t-win_amd64.whl", hash = "sha256:734f6e5400352ac4254456003d462866c684703570929cff7a7bde015d0cb371", size = 107386, upload-time = "2026-09-15T19:33:29.009Z" }, + { url = "https://files.pythonhosted.org/packages/2a/99/f6431c8968e89be608d74b28ae2d024521b2953f27dd44e0dece5e04f67a/yarl-1.25.1-cp314-cp314t-win_arm64.whl", hash = "sha256:287e99ff5aa4dc1c7630bfc683ded6f106d756c99dec432a2d7f197a784f51c6", size = 102094, upload-time = "2026-09-15T19:33:31.151Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3b/4f51eab40c2eabea6c3d5b121dff4b8988dc35087732ffede12d2be8b8dd/yarl-1.25.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9b1bdaae98bc016825dd3c9d8ee1832f829b3341f9cc6ebd1a1b0a7fef7367cc", size = 143875, upload-time = "2026-09-15T19:33:33.52Z" }, + { url = "https://files.pythonhosted.org/packages/41/05/bbd58fc063f5f299a883f810760b265ca26c8167c91cb9a494d0fe2387e1/yarl-1.25.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:e7011b8fb8c4054bf0c12e5edc6cd83778b0028e99ce59b18586ed036f92cfdc", size = 104028, upload-time = "2026-09-15T19:33:36.22Z" }, + { url = "https://files.pythonhosted.org/packages/12/ee/2fba0aecb52e7020e189f684148783aa0b9cfa3b3bfb0b400646eef70ad4/yarl-1.25.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:f074e8d4aa0a5798920ddb6de3d08b228c614ff3724c3e8bd7577f4bafea867b", size = 104041, upload-time = "2026-09-15T19:33:38.86Z" }, + { url = "https://files.pythonhosted.org/packages/38/35/884beab53ed88c7247d1671972b5ef116f7351fe0c7e6de8c3558372cb16/yarl-1.25.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d42e7e3ca399555578b4d617e3a6ecf13371b3743a115995fa010c7bf341459", size = 116018, upload-time = "2026-09-15T19:33:44.265Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/1a91b685afb55cc18608443ace95280e96e263a97565811732b6788d3269/yarl-1.25.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aa4ed3dd308548f9e707d9caaf005d2d7f8c1e7868f858dfeb47fe76e16b391d", size = 107033, upload-time = "2026-09-15T19:33:46.45Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c1/58b379fcb1d68d907b7fcf75200c44321896509b2a6a74abbb4b19d864d2/yarl-1.25.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42a66563d8cc056ee32e6191e05097a7b2b3bc302e0bc3133daf8710eb18bd26", size = 123257, upload-time = "2026-09-15T19:33:48.631Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2d/1fe96cf5c2aeab10095e48f38585cf5a8451fb7253234822398e52aa5336/yarl-1.25.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98d370568f393215d605304cdb77b3d5539bd192c75b623c7304c42c8d6d8273", size = 126745, upload-time = "2026-09-15T19:33:50.999Z" }, + { url = "https://files.pythonhosted.org/packages/ad/60/8674394ce43f4dadae573a1d6f451716438e9eab7d7fe8d643c673a32d85/yarl-1.25.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23bf5b403c879a54964e0feac7285688e04bb220074878d737d331522da0a5bf", size = 117255, upload-time = "2026-09-15T19:33:53.456Z" }, + { url = "https://files.pythonhosted.org/packages/2f/72/0faa30e02605d56127d42bb987dcc97da3863b7bf70b9bfbf5f739c05e30/yarl-1.25.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d45673badd08456d0340e9364eddafe1c53a9d2896424294de4d7dd71ad3ee57", size = 115166, upload-time = "2026-09-15T19:33:55.669Z" }, + { url = "https://files.pythonhosted.org/packages/8c/90/9a46eac564c437e128285c5c1d7bb385d268394e9209d84f6bf4a14471ef/yarl-1.25.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:0136d640dfa9b0523853e411430a99f8a91eca85774c6420285a33b755bc6de3", size = 116082, upload-time = "2026-09-15T19:33:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/ad/38/4b1a686a3758878f93d2f1ea943f5a165f3555769cd16e761cfd0efdca17/yarl-1.25.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:59ba3a6e1aa8cfe5adf4bd270fd965db21955401b7ca6f1696010c55ed4daec2", size = 108048, upload-time = "2026-09-15T19:34:00.25Z" }, + { url = "https://files.pythonhosted.org/packages/da/14/f348eb967f31a58348612a2b93bd8a2ab664548e2b5e879cac7f592f7201/yarl-1.25.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:87796fedc3ba97ec14fab55acb48584276e6c1e4c1e89c422bda62c838e754a9", size = 122775, upload-time = "2026-09-15T19:34:02.455Z" }, + { url = "https://files.pythonhosted.org/packages/ab/e9/4f7b79700f88cb9e8bb66f8b54f9bce1844c013a2c39fdc47112e9334c95/yarl-1.25.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:bd0912757081f89b107d6c00b2ff8a194401b0b87eadcf4481de2b865a8fd44f", size = 115096, upload-time = "2026-09-15T19:34:05.283Z" }, + { url = "https://files.pythonhosted.org/packages/cf/37/f9cb020331997d3eb887bd28d5410ecfd3d80bf163c23d7cec490d78dade/yarl-1.25.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:b51c159a9794633f5e0db7ecec7b2b6e3734eca1f5d17dc989ff3552a43ff78b", size = 120655, upload-time = "2026-09-15T19:34:07.382Z" }, + { url = "https://files.pythonhosted.org/packages/12/83/52fceb22891a41f168db7ec22fd1d81e06b6a0b8d9f70921bd3e785defd0/yarl-1.25.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:319e070a01db9920fb63761843f96a104c8e2b9427266731810dc1e22595b17c", size = 117488, upload-time = "2026-09-15T19:34:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5c/ce6c4ff1247fcbe4b33d462c23a097106d909b173fde7042bc52290466e2/yarl-1.25.1-cp315-cp315-win_amd64.whl", hash = "sha256:a2059a2d891bd156bc5184e7ab7a56e78a84dfcfdeac8c501b552533ad1c36ee", size = 103434, upload-time = "2026-09-15T19:34:12.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/766a906b0704fb26d52b19dc22bed48a8ba0544203b70dcf44da350e8194/yarl-1.25.1-cp315-cp315-win_arm64.whl", hash = "sha256:a78b50b4f7918a3de71105d5c0b93bbc57bb8339a4d03a9dfd449f9068e76f3d", size = 99155, upload-time = "2026-09-15T19:34:15.132Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d3/a1d09b32cb6ab14f66b44939f5b4255b8b9e747aef3974af1d5d80ccc2fd/yarl-1.25.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:b5402a340723fa7da00b5cff987ddab61276be6d11251ea71ae02bcac54890d8", size = 149284, upload-time = "2026-09-15T19:34:17.452Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/fe554d879692650bca70bfbc0df124e82e4d2bb7456f698c7756f1279a96/yarl-1.25.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:eda19ea5ee88742f47a2340816e6f2d40b53bed3ab5b69794769f36af9f35bb4", size = 106399, upload-time = "2026-09-15T19:34:21.474Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4b/e7af56177ac8d40094c82d7728224c0b8472157d50d362e5fb3b014b2bc8/yarl-1.25.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:75baa6cf9b6d1c52f3e111a130e202fd8cf0a5b3a066c3f73d615e885092e4ec", size = 106968, upload-time = "2026-09-15T19:34:23.619Z" }, + { url = "https://files.pythonhosted.org/packages/da/4f/2df41fd738d46f23ef829ae8b4468d94bb6070038fc6dfab6165ed44fea8/yarl-1.25.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbcef5a9119ef653653132cccaf999b30a0af6f33bb0a4ba80bec30056868487", size = 114717, upload-time = "2026-09-15T19:34:25.879Z" }, + { url = "https://files.pythonhosted.org/packages/69/ea/002b66df53bbd1aed1c23358ff99c9bdc744fe3f4d2740e1b2fdd7192885/yarl-1.25.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7efc9f082dfed77c316edffa9deb52888e1bc6789171887cc1f68e06d65465c8", size = 105198, upload-time = "2026-09-15T19:34:28.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/7c/95c8bc0c8f97d71e59c94525ad60d76f5c57d3f2820f08137ca8b9f0542a/yarl-1.25.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe01645169a2112aa1d4ebc3e4c5f029c5c8f97adfc32e5d37c993b39a994d75", size = 120271, upload-time = "2026-09-15T19:34:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7a/6fe9da56ec77927baa669fd86c39c567ce6205bab53d581082c6744c8ae7/yarl-1.25.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1c557dfd5e3db046053a0bdc72261ade790ebe8e2c7a41b36b0ca1f14cb95f3", size = 123572, upload-time = "2026-09-15T19:34:32.73Z" }, + { url = "https://files.pythonhosted.org/packages/8b/83/35f222d17fa70a14c7c74fdf112ccf5515e0c2a87082b1f9b99f7693bf57/yarl-1.25.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ce4d6ccafb33d39bd78444612d14938ead674c25702ded2ee9c54a47735d225", size = 115228, upload-time = "2026-09-15T19:34:35.344Z" }, + { url = "https://files.pythonhosted.org/packages/da/6f/fbaaf619423578a7d898d0f226ae47bc1906c293865c416d83c17b828b0e/yarl-1.25.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80a063f8297fc796296f00f100be520f209b23dc98f93ce8eba6ee7122598209", size = 111618, upload-time = "2026-09-15T19:34:37.656Z" }, + { url = "https://files.pythonhosted.org/packages/ba/78/7383278f1b3cf8e0496bd95b3281a7b09b89217b6b428db24c6b99b3deca/yarl-1.25.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:7cb414a73e21a7ab58254926073f2930cb22f5b4314ea4260a687e2b3fd4dce3", size = 114839, upload-time = "2026-09-15T19:34:40.099Z" }, + { url = "https://files.pythonhosted.org/packages/62/49/5506e5b6d29aab91bd845cc9016d88c3d3f81b81bc242b8100bdd5737825/yarl-1.25.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:85a18376073f8a39aa07be34f9fc77e2869aa72c55c441efdd2cf79a0407504d", size = 106212, upload-time = "2026-09-15T19:34:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/19877c193b7c5929f4b07118c18bbe390f3fadd0f59dd98c0cd12b31fa5c/yarl-1.25.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:77716e245c90f058466a05e6a465bb8600f767a8f4b18b4d40f3aff958e5f73c", size = 119985, upload-time = "2026-09-15T19:34:44.976Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6c/0a46fbbf9ecbcbd0cc20d2193394254b9e19817f22c814aab60f99847400/yarl-1.25.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:1e80dcf1446e1b080b1932b0d103c464a04112f5bc31f0f983ad418172063cde", size = 112081, upload-time = "2026-09-15T19:34:47.45Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/93f5d471230c74ccd06255d0842739f86551f937f9e63a5e947853c6244a/yarl-1.25.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:bdc8d8b8c22e9e43ac68316b5e6cf083dec537f4ec213cb4aa967b583bc3fa64", size = 116995, upload-time = "2026-09-15T19:34:49.972Z" }, + { url = "https://files.pythonhosted.org/packages/2b/80/c386593035ee3f9c6c6af0847b5578f2830c674794a9d7701b744a3ebd42/yarl-1.25.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbf531053a0935f2e871bcd4753f90313688772ff8c017f5ea402e315a78c1f", size = 115570, upload-time = "2026-09-15T19:34:52.628Z" }, + { url = "https://files.pythonhosted.org/packages/38/02/eef443559563ef8f2e10469387b8b1e97cb5efee95b288a56da60801f7ee/yarl-1.25.1-cp315-cp315t-win_amd64.whl", hash = "sha256:b13b88747769537f3d32e89e3a735da10c0a9e35d7322928c701b5f93d3afffd", size = 106811, upload-time = "2026-09-15T19:34:54.935Z" }, + { url = "https://files.pythonhosted.org/packages/88/91/41e284ca2cf5211e05dae031d126a3668aea88fa759df56e7e35c6ad25ba/yarl-1.25.1-cp315-cp315t-win_arm64.whl", hash = "sha256:783dd1467083f4d3f7722ad6a313f24c173e7571372738fcb7a6e6d1ba48df25", size = 101804, upload-time = "2026-09-15T19:34:57.231Z" }, + { url = "https://files.pythonhosted.org/packages/54/22/318c7980066769c6bcd9221ed2248294f5698811da099013098c670565ed/yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3", size = 63617, upload-time = "2026-09-15T19:34:59.616Z" }, +] + +[[package]] +name = "zipp" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/23/655a1802fe8041302c959774ca7c80b53bc24737ff3ef45cb50ef11bd96c/zipp-4.1.1.tar.gz", hash = "sha256:7ebb7a44c021b29fd8dbd7cce6812d0d7b5b454521f93cc71af6ccd155aaa70b", size = 27649, upload-time = "2026-10-03T17:03:03.452Z" } wheels = [ - { url = "http://mirrors.i.h.pjlab.org.cn/repository/pypi-proxy/packages/typing-extensions/4.16.0/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8" }, + { url = "https://files.pythonhosted.org/packages/b5/98/df615823cd9419131ce19fba00de53a663794369e198aade064a244b385d/zipp-4.1.1-py3-none-any.whl", hash = "sha256:8979f52d874162f485ff2981e3891f3a3317b7a3dd43ff1e1775b9304f307a9c", size = 10582, upload-time = "2026-10-03T17:03:02.506Z" }, ]