diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bc1f755 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +**/.git +**/.venv* +**/__pycache__ +**/.pytest_cache +**/.ruff_cache +**/.DS_Store +**/*.local.* +examples/local/ +**/runs/ +**/base-control.json +**/arms-control.json +artifacts/ +checkpoints/ +work/ +.third-party/ +third_party/wirelesscomm/ +**/.env +**/.env.* +**/*.pem +**/*.key +**/owner-access diff --git a/.github/workflows/recipe-software.yml b/.github/workflows/recipe-software.yml new file mode 100644 index 0000000..65d2e0f --- /dev/null +++ b/.github/workflows/recipe-software.yml @@ -0,0 +1,119 @@ +name: Recipe software + +# Exercise production installers/images only when their inputs change. +# Documentation-only PRs retain the existing CPU, lint and docs checks. +on: + push: + branches: [main] + paths: + - ".github/workflows/recipe-software.yml" + - ".dockerignore" + - "Dockerfile" + - "compose.yaml" + - "pyproject.toml" + - "uv.lock" + - "src/**" + - "examples/*.py" + - "examples/run.sh" + - "examples/xlerobot_snack_delivery/**" + - "!examples/xlerobot_snack_delivery/**/*.md" + - "examples/microduck_vln/**" + - "!examples/microduck_vln/**/*.md" + - "integrations/xlerobot_owner/**" + - "!integrations/xlerobot_owner/**/*.md" + - "integrations/microduck_vln/**" + - "!integrations/microduck_vln/**/*.md" + - "agents/**" + - "!agents/**/*.md" + - "scripts/check_recipe_software.py" + pull_request: + paths: + - ".github/workflows/recipe-software.yml" + - ".dockerignore" + - "Dockerfile" + - "compose.yaml" + - "pyproject.toml" + - "uv.lock" + - "src/**" + - "examples/*.py" + - "examples/run.sh" + - "examples/xlerobot_snack_delivery/**" + - "!examples/xlerobot_snack_delivery/**/*.md" + - "examples/microduck_vln/**" + - "!examples/microduck_vln/**/*.md" + - "integrations/xlerobot_owner/**" + - "!integrations/xlerobot_owner/**/*.md" + - "integrations/microduck_vln/**" + - "!integrations/microduck_vln/**/*.md" + - "agents/**" + - "!agents/**/*.md" + - "scripts/check_recipe_software.py" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: recipe-software-${{ github.ref }} + cancel-in-progress: true + +jobs: + recipe-software: + name: Recipe software (${{ matrix.recipe }}, native + Docker) + runs-on: ubuntu-24.04 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - recipe: xlerobot + project: xlerobot_snack_delivery + target: xlerobot-software + - recipe: microduck + project: microduck_vln + target: microduck-software + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.12.x" + enable-cache: false + + - name: Install the native software Recipe from its frozen lock + env: + UV_CACHE_DIR: ${{ runner.temp }}/recipe-downloads + run: | + uv sync --frozen --python 3.12 --no-default-groups --group host + EMBODIRUN_SCENE_PYTHON="$RUNNER_TEMP/recipe environment/bin/python" \ + .venv/bin/embodirun example "examples/${{ matrix.project }}/example.yaml" setup --mode software + + - name: Check both native entrypoints and printed commands + run: | + "$RUNNER_TEMP/recipe environment/bin/python" scripts/check_recipe_software.py \ + --recipe "${{ matrix.recipe }}" --report "$RUNNER_TEMP/recipe-native.json" + + - name: Set up an isolated Docker builder + uses: docker/setup-buildx-action@v4 + + - name: Build the production software Docker target + run: docker buildx build --load --no-cache --target "${{ matrix.target }}" --tag recipe-software-ci . + + - name: Check the offline container as the host UID and compare locked inventories + run: | + docker run --rm --network none --user "$(id -u):$(id -g)" \ + --mount "type=bind,source=$PWD/scripts/check_recipe_software.py,target=/check.py,readonly" \ + --mount "type=bind,source=$RUNNER_TEMP,target=/workspace" \ + --entrypoint /opt/venv/bin/python recipe-software-ci /check.py \ + --recipe "${{ matrix.recipe }}" --source-root /opt/embodirun \ + --work-dir /workspace --report /workspace/recipe-docker.json + python3 - <<'PY' + import json, os, pathlib + root = pathlib.Path(os.environ["RUNNER_TEMP"]) + native = json.loads((root / "recipe-native.json").read_text()) + docker = json.loads((root / "recipe-docker.json").read_text()) + assert native["packages"] == docker["packages"], "Native/container locked packages differ" + print(f"Native/container parity: {len(native['packages'])} packages") + PY diff --git a/.gitignore b/.gitignore index 2e38654..5dbc495 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,11 @@ site/ site-zh/ .tools/ .os-review/ + +# Generated local recipe workspaces and optional recipe environments +/examples/local/ +**/runs/ +**/base-control.json +**/arms-control.json +/.venv-xlerobot-snack/ +/compose.local.yaml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dede1b9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim AS host +RUN apt-get update && apt-get install -y --no-install-recommends git openssh-client ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /opt/embodirun +COPY pyproject.toml uv.lock README.md LICENSE NOTICE THIRD_PARTY_NOTICES.md ./ +COPY src/ src/ +COPY examples/ examples/ +COPY agents/ agents/ +COPY integrations/ integrations/ +# Checkout file modes can be owner-only; the documented Compose flow runs as +# the host UID so generated recipe files stay editable outside the container. +RUN chmod -R a+rX /opt/embodirun +ENV UV_LINK_MODE=copy UV_PROJECT_ENVIRONMENT=/opt/venv \ + PATH="/opt/venv/bin:$PATH" PYTHONPATH=/opt/embodirun \ + EMBODIRUN_SOURCE_ROOT=/opt/embodirun EMBODIRUN_SCENE_PYTHON=/opt/venv/bin/python \ + EMBODIRUN_EXAMPLE_PYTHON=/opt/venv/bin/python \ + PYTHONUNBUFFERED=1 +RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \ + python examples/setup_environment.py host --environment /opt/venv +ARG EMBODIRUN_REVISION=unknown +ENV EMBODIRUN_REVISION=$EMBODIRUN_REVISION +ENTRYPOINT ["embodirun", "example"] +CMD ["--help"] + +# Rehearsal uses the same small recipe lock as native setup --mode software. +FROM host AS xlerobot-software +RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \ + python examples/setup_environment.py snack --mode software --environment /opt/venv + +# Local Linux robot host. Device access and verified calibration are supplied at run time. +FROM xlerobot-software AS xlerobot +RUN apt-get update && apt-get install -y --no-install-recommends libglib2.0-0 libgl1 ffmpeg build-essential \ + && rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \ + python examples/setup_environment.py snack --environment /opt/venv +# Compose can run as an arbitrary host UID without a passwd entry. Torch asks +# getpass for a username and uses the home directory for runtime caches. +ENV LOGNAME=embodirun USER=embodirun HOME=/tmp + +# Offline configuration checks use the same small Recipe lock as native setup. +FROM host AS microduck-software +RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \ + python examples/setup_environment.py microduck --mode software --environment /opt/venv + +# Add simulation and inference from the same lock, separate from the robot-owner profile. +FROM microduck-software AS microduck +RUN apt-get update && apt-get install -y --no-install-recommends libegl1 libgl1 libglfw3 libopengl0 ffmpeg build-essential \ + && rm -rf /var/lib/apt/lists/* +COPY third_party/embodiinfer/ third_party/embodiinfer/ +RUN chmod -R a+rX third_party/embodiinfer +RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \ + test -f third_party/embodiinfer/embodiinfer/__init__.py \ + && python examples/setup_environment.py microduck --mode simulation --environment /opt/venv +ENV LOGNAME=embodirun USER=embodirun HOME=/tmp +ENV MUJOCO_GL=egl PYOPENGL_PLATFORM=egl NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics diff --git a/README.md b/README.md index 869acef..149b014 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ planning loop. Click a preview to watch the video and explore the setup. [Reproduce the demos](docs/en/examples.md) with versioned YAML configurations and -the shared `examples/run.sh` launcher. +the shared `embodirun example` CLI (`examples/run.sh` remains available). ## Why EmbodiRun? diff --git a/README.zh-CN.md b/README.zh-CN.md index 559886c..5a6439b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -38,7 +38,7 @@ 点击预览图观看视频,了解运行配置。 -[复现演示](docs/zh/examples.md):使用统一 YAML 配置与 `examples/run.sh` 启动入口。 +[复现演示](docs/zh/examples.md):使用统一 YAML 配置与 `embodirun example` 命令行入口(`examples/run.sh` 仍可使用)。 ## 为什么选择 EmbodiRun? diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..1358187 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,82 @@ +services: + host: + build: + context: . + target: host + image: embodirun-examples:host + init: true + stdin_open: true + tty: true + volumes: + - ./examples/local:/workspace + - launcher-tmp:/tmp + command: [--help] + xlerobot-software: + build: + context: . + target: xlerobot-software + image: embodirun-examples:xlerobot-software + init: true + stdin_open: true + tty: true + volumes: + - ./examples/local:/workspace + - launcher-tmp:/tmp + command: [--help] + xlerobot: + profiles: [hardware] + build: + context: . + target: xlerobot + image: embodirun-examples:xlerobot + init: true + stdin_open: true + tty: true + network_mode: host + stop_grace_period: 70s + volumes: + - ./examples/local:/workspace + - launcher-tmp:/tmp + # Add explicit devices and read-only SDK/calibration mounts in compose.local.yaml. + # No privileged mode or automatic hardware startup. + command: [--help] + microduck-software: + build: + context: . + target: microduck-software + image: embodirun-examples:microduck-software + init: true + stdin_open: true + tty: true + volumes: + - ./examples/local:/workspace + - launcher-tmp:/tmp + command: [--help] + microduck: + profiles: [gpu] + build: + context: . + target: microduck + image: embodirun-examples:microduck + runtime: nvidia + init: true + stdin_open: true + tty: true + stop_grace_period: 70s + volumes: + - ./examples/local:/workspace + - launcher-tmp:/tmp + - ${MICRODUCK_ASSETS:-./examples/local/microduck/assets}:/assets:ro + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] + command: [--help] + +volumes: + # Keep private launcher sockets reachable from another same-UID Compose run. + # Default copy-up retains the image's /tmp permissions; this volume is project-scoped. + launcher-tmp: diff --git a/docs/en/examples.md b/docs/en/examples.md index 841eed8..5f65ef4 100644 --- a/docs/en/examples.md +++ b/docs/en/examples.md @@ -1,6 +1,6 @@ # Reproduce the demos -The four demos use one entrypoint, `examples/run.sh`, and versioned YAML +The four demos use one entrypoint, `embodirun example`, and versioned YAML manifests. Deployment files describe devices and services; the example manifest selects the task, execution limits, and output directory. @@ -17,17 +17,24 @@ From the repository root: ```bash uv sync --frozen -bash examples/run.sh examples/multi_robot_serving/example.yaml validate -bash examples/run.sh examples/multi_robot_serving/example.yaml plan +uv run --frozen embodirun example init so101 +CONFIG=examples/local/so101/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan ``` `validate` checks fields and deployment references locally; `plan` prints the commands that a run would execute. -Copy the selected YAML and its referenced deployment to `*.local.yaml` in the -same directory. Update the reference in `parameters.deployment`, then fill in -device paths, SSH hosts, calibration, checkpoint paths, and task settings. -Local YAML/JSON files are ignored by Git. +`init` creates a new ignored directory containing a manifest and its linked +deployment files. It does not overwrite existing configuration. Choose +`xlerobot`, `microduck`, `embodiinfer-http`, `embodiinfer-wireless`, or +`sglang-http` instead of `so101` for those recipes. Fill in device paths, SSH +hosts, calibration, checkpoint paths, and task settings before deployment. +For SO-101 and XLeRobot, `embodirun example "$CONFIG" check` lists local +missing prerequisites without opening devices. It does not establish live +hardware or model readiness. MicroDuck's `check` additionally runs a GPU/EGL +scene preflight on the target Linux host. Manifest paths resolve relative to the YAML file. Deployment device and model paths belong to the node where they are used. The complete field and command @@ -38,33 +45,36 @@ reference is in [examples/README.md](https://github.com/BUAA-CI-LAB/EmbodiRun/bl After following the selected example's calibration and environment instructions: ```bash -CONFIG=examples/multi_robot_serving/example.local.yaml -bash examples/run.sh "$CONFIG" setup -bash examples/run.sh "$CONFIG" up --allow-hardware -bash examples/run.sh "$CONFIG" run --allow-hardware -bash examples/run.sh "$CONFIG" down +CONFIG=examples/local/so101/example.local.yaml +uv run --frozen embodirun example "$CONFIG" setup +uv run --frozen embodirun example "$CONFIG" check +uv run --frozen embodirun example "$CONFIG" up --allow-hardware +uv run --frozen embodirun example "$CONFIG" run --allow-hardware +uv run --frozen embodirun example "$CONFIG" down ``` SO-101 services remain running between tasks. Reset the scene manually and use `down` when finished. XLeRobot keeps `up` in the foreground; run the task in -a second terminal, then Ctrl-C in the service terminal to stop its stack. +a second terminal, then use `down` or Ctrl-C to stop its stack. Keep an operator present and verify the emergency stop before enabling motion. For a hardware-free task rehearsal: ```bash -bash examples/run.sh examples/xlerobot_snack_delivery/example.yaml dry-run +uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml dry-run ``` ## Run the simulator -Follow [MicroDuck setup](microduck-vln.md) to install its optional environment -and prepare the scene and checkpoint. Fill in `example.local.yaml`, then run: +Follow [MicroDuck setup](microduck-vln.md) to prepare the external scene and +checkpoint. On the Linux GPU host, run: ```bash -CONFIG=examples/microduck_vln/example.local.yaml -bash examples/run.sh "$CONFIG" check -bash examples/run.sh "$CONFIG" run +uv run --frozen embodirun example init microduck --assets /absolute/asset/root +CONFIG=examples/local/microduck/example.local.yaml +uv run --frozen embodirun example "$CONFIG" setup +uv run --frozen embodirun example "$CONFIG" check +uv run --frozen embodirun example "$CONFIG" run ``` `check` verifies assets and the GPU/rendering environment. The YAML selects diff --git a/docs/en/installation.md b/docs/en/installation.md index 6255724..ef98c92 100644 --- a/docs/en/installation.md +++ b/docs/en/installation.md @@ -24,6 +24,74 @@ uv sync --frozen development tools. It is enough for `embodirun validate`, the Host lifecycle, and the test suite. +For runnable recipes, use the same source checkout as the entrypoint: + +```bash +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init xlerobot +CONFIG=examples/local/xlerobot/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" setup --mode software +uv run --frozen embodirun example "$CONFIG" check --mode software --json +uv run --frozen embodirun example "$CONFIG" dry-run +``` + +`init` creates linked, editable files in a new ignored directory. `setup` +uses Python 3.12 and uv 0.12.x. For custom `--output`, prefer +`examples/local/` or a directory outside the checkout +so generated settings and credentials stay outside the Docker build context. +`runs/` and generated Control credential files are excluded from Git and Docker. +The checkout Host environment uses the root `uv.lock` and includes the default +development group. The separate XLeRobot environment defaults to +`.venv-xlerobot-snack` and uses the dependency project and `uv.lock` under +`examples/xlerobot_snack_delivery/` for both modes: + +| Mode | Installs | Next check | +|---|---|---| +| `setup --mode software` | Small runtime environment without owner or PyTorch | `check --mode software --json`, then fixture `dry-run` | +| `setup --mode hardware` | Full owner extra, including locked LeRobot, CPU PyTorch, cameras, and recording | `check --mode hardware --json`, then operator verification | + +`setup` / `check` default to hardware mode when `--mode` is omitted. Hardware +check reports the installed LeRobot parent directory as `environment.sdk_src`; +use it for `hardware.local.json`'s `sdk_src`. A custom SDK checkout needs its own +version verification. Installing dependencies does not fill calibration, +devices, routes, or a model checkpoint. Inference runs in a separate service. +MicroDuck uses its own Python 3.12 / uv 0.12.x project and lock under +`examples/microduck_vln/`. Native and Docker `software` setup select its small +CLI/integration profile; `simulation` adds the locked simulator/inference extra +and is the default. Software checks need no assets or GPU; full simulation +still requires compatible external assets and separate CUDA/EGL/model acceptance. +See the [MicroDuck guide](microduck-vln.md). + +The separate Recipe software workflow runs for relevant source, dependency and +container changes, or by manual dispatch. Documentation-only changes use the +existing CPU, lint and documentation jobs. +Its jobs install both Recipes from frozen locks without +restored uv caches, build their production software targets in fresh Docker +builders, run both entrypoints as the host UID with container networking disabled, +and compare native/container package inventories. XLeRobot also runs its fixture +rehearsal through both entrypoints. Printed hints preserve the selected scene +Python and work outside the checkout, including paths with spaces. These checks +cover software onboarding; independent human first use and physical/model +acceptance remain follow-up work. + +If the initial `uv sync` or `docker compose build` fails before the Recipe CLI +starts, no Recipe `Outputs:` or `run.json` exists yet. Save the terminal +stdout/stderr to your chosen log file, with the exact command and exit code. +During Recipe setup, read `command-0.log` under the printed `Outputs:` directory. Keep +the first failure and exit code, then retry the same mode after correcting the +cause. For an explicit native uv HTTP read timeout, a retry can use +`UV_HTTP_TIMEOUT=300 uv run --frozen embodirun example "$CONFIG" setup --mode software` +(use hardware mode when that was the failed command). This host variable is +not automatically passed to `docker compose build`. Record download cache use +and retries; software rehearsal time does not measure a full owner installation. + +XLeRobot `check` is a read-only local check. A `passed` report still leaves +actual calibration, camera images, model output, fresh device observations, +and physical stop feedback for verification on the target host before motion. +See the [recipe](xlerobot-snack-delivery.md) and [support matrix](support-matrix.md#xlerobot-recipe-prerequisites). + ### Capability groups Choose the dependency group for the robot or simulator on each node. Use @@ -92,7 +160,11 @@ Then select the transport in the deployment YAML [`http_api.md`](http_api.md). WirelessComm peer IDs and its optional token do not encrypt or authenticate the link; use it only on a trusted network. -## Managed deployments +## Managed deployments {#managed-deployments} + +Recipe containers require Docker Engine, Compose and Buildx with BuildKit support. +Check `docker buildx version` and `docker compose version` before building; +the Dockerfile's cache mounts cannot run with the legacy builder. `embodirun init` checks out the pinned EmbodiRun and EmbodiInfer revisions in each node's deployment directory and runs `uv sync --frozen` there. @@ -104,6 +176,74 @@ for reproducibility. Initialize it only if you want the pinned tree: git submodule update --init third_party/embodiinfer ``` +The recipe container targets are `host`, `xlerobot-software`, `xlerobot`, `microduck-software`, and `microduck` in the +repository `Dockerfile`. Build them on the target Linux host, with the pinned +submodule initialized before building `microduck`: + +```bash +docker compose build host +docker compose build xlerobot-software +docker compose build microduck-software +docker compose --profile hardware build xlerobot +docker compose --profile gpu build microduck +``` + +After building `xlerobot-software`, its first software-only XLeRobot run uses the bind +mounted `examples/local` directory without installing anything on the host +beyond Docker: + +```bash +mkdir -p examples/local +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software init xlerobot --output /workspace/xlerobot +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml validate +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml plan +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml check --mode software --json +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml dry-run +``` + +`/workspace/xlerobot` inside the container is +`examples/local/xlerobot` in this checkout. The same host user owns the new +files and can edit them. The image already has the software environment, so +skip native `setup`. This dry-run uses fixtures and starts no robot owner, +inference service, or RPent. It needs no GPU runtime, devices, SDK, calibration, +or checkpoint. XLeRobot software/hardware images and native setup use the same +recipe project and lock; generic `host` remains on the root Host dependency tree. + +The `microduck-software` service uses the same Recipe lock as native MicroDuck +software setup. Use `init microduck`, then `validate`, `plan` and +`check --mode software --json`; it needs no GPU runtime or external assets. +The [MicroDuck guide](microduck-vln.md) links the complete container commands. + +When reusing a native manifest in Docker, back it up first. Its top-level +`python` overrides the image's `EMBODIRUN_SCENE_PYTHON`, so a host interpreter +path will not work in the container. Remove only `python` from the Docker copy +to select `/opt/venv/bin/python`, preserving other settings and calibration. +Keep the copy beside the original as, for example, `example.docker.yaml`, so +relative deployment/task references remain valid. In the Docker commands above, +use `/workspace/xlerobot/example.docker.yaml` as the manifest argument. +Use the native backup to return to native execution. Another option is to +initialize a separate Docker directory and migrate your deployment, hardware, +task, and routes with their path references, keeping the originals. + +The hardware profile requires explicit device mappings and read-only +calibration mounts in a local Compose override. Its locked SDK is installed +under `/opt/venv/lib/python3.12/site-packages`; set `sdk_src` to that directory, +or explicitly mount and verify a custom SDK. Model inference stays separate. +The GPU profile uses a configured +NVIDIA container runtime (`runtime: nvidia`, required by Thor's NVIDIA CSV mode) +and external assets. The Compose defaults only show +help. The five services share a Compose-project named volume at `/tmp` for +private launcher Unix sockets. A later `docker compose run` can use Recipe +`down` when it uses the same Compose project, host UID/GID, and configuration +as the active launcher. Use the same checkout or the same explicit `-p NAME` +for these commands. The volume is launcher IPC storage, not a dependency cache; +private directory/socket permissions and cleanup confirmation still apply. +You can also stop the original foreground container with Ctrl-C or +`docker stop --time 300 CONTAINER`, giving owned children time to clean up. +Then confirm physical stop feedback; launcher cleanup alone does not prove it. +ARM64 build and runtime support must be verified on each target and optional +integration; an image definition alone does not establish it. + ## PyPI `embodirun` is not published on PyPI yet. Use the source installation above. diff --git a/docs/en/microduck-vln.md b/docs/en/microduck-vln.md index 903d871..5e731bf 100644 --- a/docs/en/microduck-vln.md +++ b/docs/en/microduck-vln.md @@ -14,34 +14,104 @@ local GPU and Slurm modes, custom episode files, output schemas and troubleshoot ## Run the example -After preparing the dedicated optional environment and external assets: +Start with the software path from the repository root. It needs no scene assets, +inference submodule, Torch or GPU: ```bash +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init microduck +CONFIG="$PWD/examples/local/microduck/example.local.yaml" +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" setup --mode software +RECIPE_ENV="$PWD/.venv-microduck" +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" check --mode software --json +``` + +`init` preserves an existing directory; use `--output /absolute/new/directory` +and that manifest path for another trial. `validate` and `plan` are offline; +`plan` previews a simulation command without executing it. Software `check`, +with or without `--json`, reads configuration and installed package metadata. +CUDA/EGL, external assets, dependency imports and model execution remain +unverified. This Recipe has no fixture `dry-run`. + +For full simulation, use a Linux GPU target and fill in the manifest's external +scene root, compatible merged SFT-v3 checkpoint, episodes and asset inventory. +`setup` installs dependencies; it does not download these resources. Run the +following from the checkout root, including in a new terminal. The assignments +use the default environment and manifest; for custom paths, use the same +absolute environment directory and manifest as before. Select a custom scene +environment with `EMBODIRUN_SCENE_PYTHON` or the manifest's `python` field; an +explicit YAML `python` takes precedence, so keep it consistent: + +```bash +RECIPE_ENV="$PWD/.venv-microduck" +CONFIG="$PWD/examples/local/microduck/example.local.yaml" git submodule update --init third_party/embodiinfer -cp examples/microduck_vln/example.yaml examples/microduck_vln/example.local.yaml -# Edit the YAML with asset paths, Python environment, and resource selection. -bash examples/run.sh examples/microduck_vln/example.local.yaml check -bash examples/run.sh examples/microduck_vln/example.local.yaml run +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" setup --mode simulation +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" check --mode simulation --json && \ + "$RECIPE_ENV/bin/embodirun" example "$CONFIG" check --mode simulation && \ + "$RECIPE_ENV/bin/embodirun" example "$CONFIG" run +``` + +`setup` and `check` default to `simulation`; use `--mode software` explicitly +for the software path. Simulation `check --json` reads installed metadata, +local resource paths and the inference source entrypoint; it leaves CUDA/EGL +and scene/model readiness unverified. Simulation `check` without `--json` +continues into asset/CUDA/EGL/ONNX/MPC/video-encoder preflight after those local +checks pass. A passed preflight does not prove learned-model inference or +navigation success; `run` starts simulation episodes and its owned inference +child. + +Native `setup`, direct `uv sync` and the `microduck-software` / `microduck` +Docker targets use `examples/microduck_vln/pyproject.toml` and `uv.lock`. +Software selects the base profile; simulation adds `--extra simulation`. +Fresh target installation, native/Docker parity and GPU/model execution are +separate validation results; a resolved lock or successful software check does +not establish them. Docker commands are in the example guide. +Keep this Transformers 4.51.3 environment separate from Transformers 5.x model profiles. +Record actual commands, cache state, assistance and outcomes separately from independent human first use. + +The Recipe prints `Outputs:`; under its run directory, inspect `run.json`, +command logs and `result/` files, including `preflight.json`, `run.log` and +`inference.log`. `run` owns cleanup on exit. To request shutdown of this Recipe's +foreground launcher from another terminal, enter the same checkout root and +set the paths again. These are the defaults; if you used a custom environment +or manifest, substitute their original absolute paths: + +```bash +RECIPE_ENV="$PWD/.venv-microduck" +CONFIG="$PWD/examples/local/microduck/example.local.yaml" +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" down ``` -The example runs in simulation and uses locally prepared checkpoints, datasets, -and scene assets. The source guide also shows how to submit it with `sbatch`. +Slurm submission and `scancel` are described in the source guide. -## Reference configuration +## Reference scenario and locked profile MicroDuck is an experimental integration with a separate simulation environment. +The hardware and algorithm settings describe the earlier A800 scenario. Package +rows show the Recipe lock's resolved `simulation` candidates; this full profile +has not yet been installed or validated on the target GPU. The integration's +[package metadata](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/integrations/microduck_vln/pyproject.toml) +retains its broader version ranges. | Setting | Reference configuration | | --- | --- | | Hardware / runtime | Linux, one A800 80 GB GPU, four CPU cores, EGL; Python 3.12 | | Policy | Qwen2.5-VL-3B ActiveVLN, externally supplied merged SFT-v3 checkpoint | -| Model environment | Torch 2.11 / torchvision 0.26, Transformers 4.51.3, tokenizers 0.21.4 | -| Simulation environment | MuJoCo 3.8.1, ONNX Runtime 1.30, CasADi 3.7.2 | +| Locked model candidates | Linux `torch==2.10.0+cu130`, `torchvision==0.25.0+cu130`, `transformers==4.51.3`, `tokenizers==0.21.4` | +| Locked simulation candidates | `numpy==1.26.4`, `Pillow==12.3.0`, `mujoco==3.8.1`, `onnxruntime==1.30.0`, `casadi==3.7.2` | | Inference source | EmbodiInfer revision pinned by `third_party/embodiinfer` | | Execution | B=1, eager, bfloat16, SDPA, greedy decoding; recurrent session per episode | | Scene / limits | `val_2`, default spawn `(6.5, 13.8, 0)`, at most 60 primitive actions | | Success | STOP and the last three action endpoints strictly inside a 1.0 m radius | +The Torch/torchvision pair follows the pinned inference project's development +profile, whose constraints do not automatically apply to a separately installed +integration. Lock resolution and advertised ARM64 wheels establish package +candidates; the target's driver, glibc and CUDA/EGL still need validation. + ## Read the results Use the example guide's 40-episode command for an evaluation. Each run writes diff --git a/docs/en/quickstart.md b/docs/en/quickstart.md index fb50109..d1eaecf 100644 --- a/docs/en/quickstart.md +++ b/docs/en/quickstart.md @@ -11,6 +11,9 @@ uv sync --frozen ``` See [`installation.md`](installation.md) for capability groups and extras. +For a linked, editable demo configuration, run +`uv run --frozen embodirun example init xlerobot` and follow +[the recipe guide](examples.md). ## 1. No robot required diff --git a/docs/en/support-matrix.md b/docs/en/support-matrix.md index 4b27b6e..a1dea71 100644 --- a/docs/en/support-matrix.md +++ b/docs/en/support-matrix.md @@ -36,7 +36,7 @@ The roadmap tracks the next integrations separately from the current catalog. **◐ ARX5** — DM0.5 binding.
**◐ Unitree Go2** — StreamVLN navigation.
- **◐ XLeRobot** — external hardware owner. + **◐ XLeRobot** — external hardware owner; dual-arm π0.5 recipe. [Explore robot adapters](https://github.com/BUAA-CI-LAB/EmbodiRun/tree/main/src/embodirun/robots) @@ -82,7 +82,7 @@ The roadmap tracks the next integrations separately from the current catalog. | Franka FR3 | π0.5 binding | [Adapter](https://github.com/BUAA-CI-LAB/EmbodiRun/tree/main/src/embodirun/robots/franka/fr3) | | ARX5 | DM0.5 binding | [Binding](https://github.com/BUAA-CI-LAB/EmbodiRun/tree/main/src/embodirun/bindings/arx/x5/dm05) | | Unitree Go2 | StreamVLN navigation | [Robot integration](https://github.com/BUAA-CI-LAB/EmbodiRun/tree/main/src/embodirun/robots/unitree/go2) | - | XLeRobot | External hardware owner | [Integration package](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/integrations/xlerobot_owner/README.md) | + | XLeRobot | External hardware owner; `lerobot.xlerobot.pi05` for two arms | [Recipe](xlerobot-snack-delivery.md) / [owner package](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/integrations/xlerobot_owner/README.md) | | SO-101 (wired teleoperation) | Multi-leader UDP fan-out, follower-side episode collection | [Integration package](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/integrations/so101_wired_teleop/README.md) | SO-101 deployments need calibrated arms, camera mappings, and a checkpoint @@ -109,6 +109,45 @@ The roadmap tracks the next integrations separately from the current catalog. [WirelessComm configuration](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/configs/http-wireless-inference/wireless.yaml) and [transport measurements](inference-transport.md). +## MicroDuck recipe prerequisites {#microduck-prerequisites} + +The [MicroDuck guide](microduck-vln.md) separates the software and simulation paths. +Native setup and both container targets share the Python 3.12 / uv 0.12.x +Recipe lock. Software `init`, `validate`, `plan` and `check --mode software` +need no GPU, model or scene. Simulation is the default setup/check mode and +adds the locked optional dependencies; scene/checkpoint/episode assets, +dependency imports, CUDA/EGL and model/navigation success require separate +target validation. The full profile remains experimental. Software CI does +not establish a new verified model/hardware combination. + +## XLeRobot recipe prerequisites {#xlerobot-recipe-prerequisites} + +Start with the [software rehearsal](xlerobot-snack-delivery.md). It uses +fixtures and needs none of the physical inputs below. Native setup uses +Python 3.12 / uv 0.12.x; the `xlerobot-software` Docker service needs Docker +Engine and Compose on Linux. Native software/hardware profiles and the +XLeRobot container targets use `examples/xlerobot_snack_delivery/uv.lock`. +The hardware owner uses CPU PyTorch; inference runs in a separate model service. + +| Input | When it is needed for your own robot | What software rehearsal uses | +|---|---|---| +| Owner environment / SDK | `setup --mode hardware` installs the owner. Set `sdk_src` to the locked LeRobot directory reported as `environment.sdk_src`; a custom SDK checkout needs its own version check. | Software dependencies; no owner or SDK import | +| Two-arm binding and checkpoint | Before connecting inference, match `lerobot.xlerobot.pi05` to an XLeRobot-adapted π0.5 checkpoint. This binding controls the arms; base routes use separate Control scope. | Fixture proposals; no checkpoint | +| Action feature names and units | Before model use, supply all 12 `left_arm_` / `right_arm_` fields: `shoulder_pan.pos`, `shoulder_lift.pos`, `elbow_flex.pos`, `wrist_flex.pos`, `wrist_roll.pos`, `gripper.pos`. Joint positions are `degrees`; grippers are `range_0_100`. | Named fixture values; no learned action-quality check | +| Camera mapping | Before hardware checks, map `observation.images.front`, `observation.images.left_wrist`, and `observation.images.right_wrist` to owner roles in deployment. Verify actual images and checkpoint input meanings before inference. | No physical cameras | +| Calibration and device paths | Before owner startup, provide your verified calibration, stable serial/camera paths, wheel geometry/directions, and limits in `hardware.local.json`. File existence does not establish calibration validity. | Template placeholders are allowed | +| Routes | `dry-run` accepts the supplied `fixture: true` routes. Hardware preparation needs locally recorded, directed non-fixture chunks; verify each route in the target space before motion. | Zero-motion fixtures | +| Handover | Before supervised delivery, validate `handover.forward_pose` and `gripper_opening` for your calibrated robot and workspace. | Fixture task sequence | +| Model endpoint | Set `deployment.local.yaml`'s `model.endpoint` before startup; verify the service and named outputs separately before model-driven motion. | No model connection | +| Physical stop | With the operator present, obtain fresh owner stop confirmation before motion and after stopping. An accepted stop request or zero wheel speed alone is insufficient. | Simulated feedback | + +`check --mode hardware --json` reads local files and declarations. Exit 0 / +`status: "passed"` does not verify device connections, actual calibration, +camera images, model output, fresh observations, stop feedback, or task success. +The default `setup` / `check` mode is hardware; use `--mode software` explicitly +for rehearsal. Linux ARM64 dependencies and optional integrations need checks +on the target. A container image alone does not establish hardware/GPU support. + ## Roadmap
diff --git a/docs/en/xlerobot-snack-delivery.md b/docs/en/xlerobot-snack-delivery.md index 43afa9e..0355791 100644 --- a/docs/en/xlerobot-snack-delivery.md +++ b/docs/en/xlerobot-snack-delivery.md @@ -31,6 +31,8 @@ The configuration, launcher, and task code are under | Interfaces and execution feedback | [`architecture.md`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/architecture.md) | | Deployment fields | [`deployment.example.yaml`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/deployment.example.yaml), [`config.example.json`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/config.example.json) | | Unified setup and launch | [`example.yaml`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/example.yaml), [`examples/run.sh`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/run.sh) | +| Deployment help from your coding Agent | [`AGENT_GUIDE.md`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/AGENT_GUIDE.md) | +| Classroom trial and first-use feedback | [`FIRST_USE_FEEDBACK.md`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/FIRST_USE_FEEDBACK.md) | The default route files are marked as fixtures and are accepted only by `dry-run`. For a real scene, record directed route chunks in the target space @@ -40,18 +42,54 @@ route files. ## Rehearse without hardware -After `uv sync --frozen`, rehearse the task through the shared entrypoint: +Choose native setup or the [Docker software path](installation.md#managed-deployments). +Both need a checkout on the target Linux host and network access for the first +installation. Native XLeRobot setup uses Python 3.12 and uv 0.12.x: ```bash -bash examples/run.sh examples/xlerobot_snack_delivery/example.yaml validate -bash examples/run.sh examples/xlerobot_snack_delivery/example.yaml dry-run +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init xlerobot +CONFIG=examples/local/xlerobot/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" setup --mode software +uv run --frozen embodirun example "$CONFIG" check --mode software --json +uv run --frozen embodirun example "$CONFIG" dry-run ``` -The package README covers local YAML setup, service startup, supervised -execution, and output files. See [Reproduce the demos](examples.md) for common -commands. +No real SDK, calibration, cameras, route recording, checkpoint, model endpoint, +RPent, or GPU is required. If `init` finds an existing directory, reuse the +configuration path it prints or choose a new `--output`; it does not overwrite +local work. Docker uses `xlerobot-software` and already contains the software +environment, so it skips native `setup`. Its dependency source is the same +recipe project and lock as native XLeRobot setup. -For hardware, follow the owner and safety instructions in the setup guide, -keep an operator present, and validate the robot-specific calibration and -emergency stop before allowing motion. Record routes and calibrate the arm and -cameras for your own workspace. +`check` exits 0 with `status: "passed"` or 2 with issues to resolve. Read each +issue's `location` and `next_action`, then repeat the same check. The printed +output directory contains `run.json`, `command-0.log`, and +`result/status.json` / `result/events.jsonl`. A completed dry-run records +fixture sequencing, with `task_success: "unverified"` and +`physical_success: null`; it starts no owner, inference service, or task Agent. + +## Prepare your own cart + +Continue in the generated local directory: + +| File | Your inputs | +|---|---| +| `deployment.local.yaml` | Model endpoint, Control ports, camera feature-to-owner-role mapping | +| `hardware.local.json` | Installed SDK path, devices, verified calibration, wheel parameters, limits | +| `config.local.json` and `routes/` | Recorded directed routes, grasp instruction, planner, calibrated handover | + +Use `setup --mode hardware` for the complete owner environment, then +`check --mode hardware --json` for local prerequisites. These commands default +to hardware mode when `--mode` is omitted. See the +[support matrix](support-matrix.md#xlerobot-recipe-prerequisites) for the +two-arm `lerobot.xlerobot.pi05` binding, units, cameras, and required inputs. + +Static checks do not establish live readiness. Follow the setup guide and keep +an operator present to verify calibration, camera images, model compatibility, +and fresh stop feedback before authorizing motion. Inference runs +separately; installing the owner does not supply a checkpoint. Use the local +README for supervised `up` / `run` and shutdown. Classroom feedback materials +are provided, but actual student feedback still needs to be collected. diff --git a/docs/zh/examples.md b/docs/zh/examples.md index 02252b2..d402d84 100644 --- a/docs/zh/examples.md +++ b/docs/zh/examples.md @@ -1,6 +1,6 @@ # 复现演示 -四个演示均通过 `examples/run.sh` 启动,并使用带格式版本号的 YAML 配置。 +四个演示均通过 `embodirun example` 启动,并使用带格式版本号的 YAML 配置。 部署配置描述设备和服务,示例配置指定任务、执行限制和输出目录。 | 演示 | 示例目录 | 运行内容 | @@ -16,17 +16,22 @@ ```bash uv sync --frozen -bash examples/run.sh examples/multi_robot_serving/example.yaml validate -bash examples/run.sh examples/multi_robot_serving/example.yaml plan +uv run --frozen embodirun example init so101 +CONFIG=examples/local/so101/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan ``` `validate` 在本地检查字段和部署引用;`plan` 打印一次运行 将要执行的命令。 -将选定的 YAML 及其引用的部署复制到同一目录下的 `*.local.yaml`。 -更新 `parameters.deployment` 中的引用,然后填写 -设备路径、SSH 主机、标定、检查点路径和任务设置。 -本地 YAML/JSON 文件会被 Git 忽略。 +`init` 一次生成独立的本地目录,示例配置与引用的部署文件已联动, +且不会覆盖已有目录。其他模板可选 `xlerobot`、`microduck`、 +`embodiinfer-http`、`embodiinfer-wireless` 和 `sglang-http`。 +随后填写设备路径、SSH 主机、标定、检查点路径和任务设置。 +`examples/local/` 会被 Git 忽略。SO-101 和 XLeRobot 的 `check` +只列出本地缺失前提,不打开设备;它不能证明机器人、相机、模型或停止反馈已就绪。 +MicroDuck 的 `check` 还会在目标 Linux 主机执行 GPU/EGL 场景预检。 示例配置中的相对路径以 YAML 所在目录为基准;部署配置中的设备和模型路径则以对应节点为准。 完整字段和命令见 [examples/README.md](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/README.md)。 @@ -36,32 +41,35 @@ bash examples/run.sh examples/multi_robot_serving/example.yaml plan 按照所选示例的标定与环境说明操作后: ```bash -CONFIG=examples/multi_robot_serving/example.local.yaml -bash examples/run.sh "$CONFIG" setup -bash examples/run.sh "$CONFIG" up --allow-hardware -bash examples/run.sh "$CONFIG" run --allow-hardware -bash examples/run.sh "$CONFIG" down +CONFIG=examples/local/so101/example.local.yaml +uv run --frozen embodirun example "$CONFIG" setup +uv run --frozen embodirun example "$CONFIG" check +uv run --frozen embodirun example "$CONFIG" up --allow-hardware +uv run --frozen embodirun example "$CONFIG" run --allow-hardware +uv run --frozen embodirun example "$CONFIG" down ``` SO-101 服务可连续运行多个任务,每次任务之间手动复位场景,全部完成后用 `down` 停止服务。 -XLeRobot 的 `up` 在前台运行;另开终端执行任务,结束后回到服务终端按 Ctrl-C 停止。 +XLeRobot 的 `up` 在前台运行;另开终端执行任务,结束后使用 `down` 或在服务终端按 Ctrl-C 停止。 启用运动前,确保操作员在场并验证急停。 无需硬件的任务预演: ```bash -bash examples/run.sh examples/xlerobot_snack_delivery/example.yaml dry-run +uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml dry-run ``` ## 在仿真中运行 -按照 [MicroDuck 环境搭建](microduck-vln.md) 安装其可选环境 -并准备场景与检查点。填写 `example.local.yaml`,然后运行: +按照 [MicroDuck 环境搭建](microduck-vln.md) 准备外部场景与检查点, +在 Linux GPU 主机执行: ```bash -CONFIG=examples/microduck_vln/example.local.yaml -bash examples/run.sh "$CONFIG" check -bash examples/run.sh "$CONFIG" run +uv run --frozen embodirun example init microduck --assets /absolute/asset/root +CONFIG=examples/local/microduck/example.local.yaml +uv run --frozen embodirun example "$CONFIG" setup +uv run --frozen embodirun example "$CONFIG" check +uv run --frozen embodirun example "$CONFIG" run ``` `check` 校验资源以及 GPU/渲染环境。YAML 选择 diff --git a/docs/zh/installation.md b/docs/zh/installation.md index 92384e7..24aeac0 100644 --- a/docs/zh/installation.md +++ b/docs/zh/installation.md @@ -23,6 +23,60 @@ uv sync --frozen `uv sync --frozen` 会安装核心包、`host` 依赖组和开发工具。 安装后即可校验配置、通过 Host 管理服务,以及运行测试。 +可运行的 Recipe 使用同一源码入口: + +```bash +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init xlerobot +CONFIG=examples/local/xlerobot/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" setup --mode software +uv run --frozen embodirun example "$CONFIG" check --mode software --json +uv run --frozen embodirun example "$CONFIG" dry-run +``` + +`init` 在新的 Git 忽略目录中生成已联动的可编辑配置。`setup` 使用 +Python 3.12 与 uv 0.12.x。自定义 `--output` 优先使用 `examples/local/` +或仓库外目录,避免生成配置与凭证进入 Docker 构建上下文;`runs/` 和生成的 +Control 凭证文件已从 Git、Docker 上下文排除。checkout 的 Host 环境使用根 `uv.lock`, +包含默认开发依赖组。独立 XLeRobot 环境默认为 `.venv-xlerobot-snack`,两种模式都使用 +`examples/xlerobot_snack_delivery/` 下的依赖项目和 `uv.lock`: + +| 模式 | 安装内容 | 下一步检查 | +|---|---|---| +| `setup --mode software` | 不含 owner 或 PyTorch 的小运行环境 | `check --mode software --json`,再执行 fixture `dry-run` | +| `setup --mode hardware` | 完整 owner extra,含锁定 LeRobot、CPU PyTorch、相机和录制依赖 | `check --mode hardware --json`,再由操作员核验 | + +省略 `--mode` 时,`setup` / `check` 默认是 hardware。硬件检查将安装的 LeRobot 包父目录 +报告为 `environment.sdk_src`,可填入 `hardware.local.json` 的 `sdk_src`;自备 SDK checkout +需单独核验版本。安装依赖不会填好标定、设备、路线或提供模型检查点,推理服务单独运行。 +MicroDuck 使用 `examples/microduck_vln/` 下独立的 Python 3.12 / uv 0.12.x +项目与锁。Native 和 Docker 的 `software` setup 选择小型 CLI/集成环境; +默认的 `simulation` 模式增加锁定的仿真与推理 extra。软件检查无需资源或 GPU; +完整仿真仍需兼容的外部资源,并另行验收 CUDA/EGL 与模型执行。 +见 [MicroDuck 指南](microduck-vln.md)。 + +独立的 Recipe software workflow 仅在相关源码、依赖或容器配置变更时运行,也支持手动触发。 +纯文档变更使用原有 CPU、lint 和文档任务。 +其任务不恢复 uv 缓存,从冻结锁安装两个 Recipe,并使用全新 Docker +builder 构建生产软件目标。它以宿主 UID、关闭容器网络运行两个入口,比较 native/container +包清单;XLeRobot 还通过两个入口执行 fixture 演练。打印的提示保留所选场景 Python, +可在仓库外执行并支持带空格路径。这些检查覆盖软件上手流程;独立人工首次使用、 +真机与模型验收仍是后续工作。 + +初始 `uv sync` 或 `docker compose build` 在 Recipe CLI 前失败时,尚无 Recipe 的 +`Outputs:` 或 `run.json`。将终端 stdout/stderr 保存到自行选择的日志文件,记录完整命令与退出码。 +Recipe setup 期间查看 `Outputs:` 目录内的 `command-0.log`。保留首条失败和退出码, +修复原因后重试同一模式。日志明确报 native uv HTTP read timeout 时,可临时执行 +`UV_HTTP_TIMEOUT=300 uv run --frozen embodirun example "$CONFIG" setup --mode software` +(失败的是 hardware 命令时重试 hardware)。宿主变量不会自动进入 `docker compose build`。 +记录下载缓存与重试情况;软件演练耗时不能代表完整 owner 安装耗时。 + +XLeRobot `check` 是只读本地检查。即使报告 `passed`,真实标定、相机图像、模型输出、 +新鲜设备观测和真机停止反馈仍需在目标主机验证,之后才能运动。 +见 [Recipe](xlerobot-snack-delivery.md) 与[支持矩阵](support-matrix.md#xlerobot-recipe-prerequisites)。 + ### 依赖组 按节点上的机器人或仿真器选择依赖组,不同设备的依赖安装在独立环境中。 @@ -88,7 +142,11 @@ uv sync --frozen --extra wireless [`http_api.md`](http_api.md)。WirelessComm 的 peer ID 及其可选 token 不会加密或认证该链路;请仅在可信网络上使用它。 -## 自动部署 +## 自动部署 {#managed-deployments} + +Recipe 容器需要 Docker Engine、Compose 和支持 BuildKit 的 Buildx。 +构建前检查 `docker buildx version` 与 `docker compose version`; +Dockerfile 的缓存挂载无法使用旧版 builder。 `embodirun init` 会在各节点的部署目录中准备指定版本的 EmbodiRun 和 EmbodiInfer 源码, 再运行 `uv sync --frozen` 安装依赖。 @@ -100,6 +158,60 @@ uv sync --frozen --extra wireless git submodule update --init third_party/embodiinfer ``` +仓库 `Dockerfile` 提供 `host`、`xlerobot-software`、`xlerobot`、`microduck-software`、`microduck` 五个目标。 +在目标 Linux 主机构建;构建 `microduck` 前先初始化上述固定子模块: + +```bash +docker compose build host +docker compose build xlerobot-software +docker compose build microduck-software +docker compose --profile hardware build xlerobot +docker compose --profile gpu build microduck +``` + +构建 `xlerobot-software` 后,通过绑定挂载的 `examples/local` 首次运行 XLeRobot 软件演练, +宿主机除 Docker 外无需安装 Recipe 依赖: + +```bash +mkdir -p examples/local +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software init xlerobot --output /workspace/xlerobot +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml validate +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml plan +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml check --mode software --json +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml dry-run +``` + +容器内的 `/workspace/xlerobot` 对应仓库的 `examples/local/xlerobot`, +使用当前宿主用户创建,便于继续编辑。镜像已有软件环境,跳过 native `setup`。 +此 `dry-run` 使用 fixture,不启动 owner、推理服务或 RPent,无需 GPU runtime、设备、SDK、 +标定或检查点。XLeRobot 软件/硬件镜像与 native setup 使用同份 Recipe 项目和锁; +通用 `host` 仍使用根项目的 Host 依赖树。 + +`microduck-software` 服务与 native MicroDuck 软件 setup 使用同份 Recipe 锁。 +执行 `init microduck`,再执行 `validate`、`plan` 与 `check --mode software --json`, +无需 GPU runtime 或外部资源。[MicroDuck 指南](microduck-vln.md)链接了完整容器命令。 + +native manifest 转 Docker 前先备份。顶层 `python` 优先于镜像的 +`EMBODIRUN_SCENE_PYTHON`,宿主解释器路径在容器中不可用。 +仅去掉 Docker 副本的 `python`,让镜像选择 `/opt/venv/bin/python`,保留其他配置和标定。 +副本留在原配置目录,例如 `example.docker.yaml`,保留 deployment/task 的相对引用; +后续 Docker 命令将 manifest 参数替换为 `/workspace/xlerobot/example.docker.yaml`。 +切回 native 时使用原备份;也可在独立 Docker 目录重新 init,再迁移 deployment、hardware、 +task 与 routes 及其路径引用,保留原始文件。 + +硬件 profile 需要在本地 Compose 覆盖文件中显式映射设备并只读挂载标定。 +锁定 SDK 已装在 `/opt/venv/lib/python3.12/site-packages`,将 `sdk_src` 指向该目录; +自备 SDK 时显式挂载并核验版本。模型推理单独运行。 +GPU profile 使用已配置的 NVIDIA 容器运行时(Thor 的 NVIDIA CSV 模式 +要求 `runtime: nvidia`)并需要外部资源。默认 Compose 命令只显示 +帮助。五个服务通过 Compose 项目内的 `/tmp` named volume 共享私有 launcher Unix socket。 +后开的 `docker compose run` 使用与运行中 launcher 相同的 Compose 项目、宿主 UID/GID +和配置,就可通过 Recipe `down` 联系它;从同一 checkout 操作,或每次使用相同的 `-p NAME`。 +该 volume 用于 launcher IPC,不是依赖缓存;私有目录/socket 权限与清理确认仍然保留。 +也可对原前台容器按 Ctrl-C,或执行 `docker stop --time 300 CONTAINER` 给子进程留出清理时间。 +之后核验真机停止反馈,launcher 清理本身不能证明真机已停止。 +ARM64 构建和运行需要在目标机器及可选集成上实测,镜像定义本身不构成支持证明。 + ## PyPI `embodirun` 尚未发布到 PyPI。请使用上文的源码安装方式。 diff --git a/docs/zh/microduck-vln.md b/docs/zh/microduck-vln.md index 2a178e7..1b86726 100644 --- a/docs/zh/microduck-vln.md +++ b/docs/zh/microduck-vln.md @@ -13,34 +13,89 @@ MPC 和 ONNX 行走策略负责执行 R2R 导航动作,同时录制第一、 ## 运行示例 -准备好独立环境、检查点、数据集和场景资源后,运行: +先在仓库根目录走软件流程,无需场景资源、推理子模块、Torch 或 GPU: ```bash +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init microduck +CONFIG="$PWD/examples/local/microduck/example.local.yaml" +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" setup --mode software +RECIPE_ENV="$PWD/.venv-microduck" +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" check --mode software --json +``` + +`init` 保留已有目录;再次试用时使用 `--output /absolute/new/directory`,并沿用新生成的配置路径。 +`validate` 和 `plan` 离线运行;`plan` 只预览仿真命令,不执行它。 +软件模式的 `check`(带或不带 `--json`)读取配置与已安装包的元数据, +CUDA/EGL、外部资源、依赖导入与模型执行仍未验证。该 Recipe 没有 fixture `dry-run`。 + +完整仿真需要 Linux GPU 目标,并在清单中填写外部场景根目录、适配的合并 SFT-v3 checkpoint、 +episode 与资源清单。`setup` 安装依赖,不下载这些资源。 +在仓库根目录执行下面的代码块,新终端也重新设置变量。代码使用默认环境和配置; +自定义路径请沿用之前环境目录与配置文件的绝对路径。 +自定义场景解释器通过 `EMBODIRUN_SCENE_PYTHON` 或配置中的 `python` 指定;显式 YAML `python` +优先,应保持它与所选环境一致: + +```bash +RECIPE_ENV="$PWD/.venv-microduck" +CONFIG="$PWD/examples/local/microduck/example.local.yaml" git submodule update --init third_party/embodiinfer -cp examples/microduck_vln/example.yaml examples/microduck_vln/example.local.yaml -# 编辑 YAML,填入资产路径、Python 环境和资源选择。 -bash examples/run.sh examples/microduck_vln/example.local.yaml check -bash examples/run.sh examples/microduck_vln/example.local.yaml run +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" setup --mode simulation +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" check --mode simulation --json && \ + "$RECIPE_ENV/bin/embodirun" example "$CONFIG" check --mode simulation && \ + "$RECIPE_ENV/bin/embodirun" example "$CONFIG" run +``` + +`setup` 与 `check` 默认是 `simulation`;软件流程每次显式使用 `--mode software`。 +仿真模式的 `check --json` 读取已安装包元数据、本地资源路径及推理源码入口, +CUDA/EGL 与场景/模型就绪仍未验证。仿真模式的 `check`(不带 `--json`)在本地前提通过后, +继续执行资源/CUDA/EGL/ONNX/MPC/视频编码器预检。预检通过不证明学习模型推理或导航成功; +`run` 才启动仿真 episode 和本轮持有的推理子进程。 + +Native `setup`、直接 `uv sync` 与 `microduck-software` / `microduck` Docker target +共用 `examples/microduck_vln/pyproject.toml` 和 `uv.lock`。 +软件模式选择基础 profile,仿真模式增加 `--extra simulation`。 +新目标主机安装、Native/Docker 一致性与 GPU/模型执行分别记录验证结果; +锁解析或软件检查通过不能证明后几项。Docker 命令见示例指南。 +Transformers 4.51.3 环境应与 5.x 模型环境分开。 +记录实际命令、缓存状态、协助和结果,并与独立人工首次使用分别报告。 + +Recipe 打印 `Outputs:`,运行目录包含 `run.json`、命令日志与 `result/`; +其中可查看 `preflight.json`、`run.log`、`inference.log`。`run` 退出时清理其子进程。 +若要从另一终端请求关闭本 Recipe 的前台 launcher,先进入同一仓库根目录并重新设置路径。 +下面使用默认路径;自定义环境或配置应填写之前使用的绝对路径: + +```bash +RECIPE_ENV="$PWD/.venv-microduck" +CONFIG="$PWD/examples/local/microduck/example.local.yaml" +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" down ``` -该示例在仿真中运行,使用本地准备好的检查点、数据集 -和场景资源。示例指南也提供了通过 `sbatch` 提交任务的方法。 +Slurm 提交与 `scancel` 见示例指南。 -## 参考配置 +## 参考场景与锁定 profile -此实验性集成使用独立的仿真环境,参考配置如下。 +此实验性集成使用独立仿真环境。硬件与算法设置来自此前的 A800 场景; +包版本行是 Recipe 锁解析出的 `simulation` 候选版本,该完整 profile 尚未在目标 GPU 上安装或验证。 +集成的[包元数据](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/integrations/microduck_vln/pyproject.toml) +仍保留较宽的版本范围。 | 设置 | 参考配置 | | --- | --- | | 硬件 / 运行时 | Linux,一块 A800 80 GB GPU,四个 CPU 核心,EGL;Python 3.12 | | 策略 | Qwen2.5-VL-3B ActiveVLN,外部提供的合并 SFT-v3 检查点 | -| 模型环境 | Torch 2.11 / torchvision 0.26,Transformers 4.51.3,tokenizers 0.21.4 | -| 仿真环境 | MuJoCo 3.8.1,ONNX Runtime 1.30,CasADi 3.7.2 | +| 锁定模型候选 | Linux `torch==2.10.0+cu130`、`torchvision==0.25.0+cu130`、`transformers==4.51.3`、`tokenizers==0.21.4` | +| 锁定仿真候选 | `numpy==1.26.4`、`Pillow==12.3.0`、`mujoco==3.8.1`、`onnxruntime==1.30.0`、`casadi==3.7.2` | | 推理来源 | 由 `third_party/embodiinfer` 固定的 EmbodiInfer 修订版本 | | 执行 | B=1,eager,bfloat16,SDPA,贪心解码;每个 episode 使用有状态会话 | | 场景 / 限制 | `val_2`,默认出生点 `(6.5, 13.8, 0)`,最多 60 个基元动作 | | 成功 | STOP 且最后三个动作端点严格位于 1.0 m 半径内 | +Torch/torchvision 组合沿用固定推理项目的开发 profile;其约束不会自动作用于独立安装的集成。 +锁解析与公开 ARM64 wheel 信息提供了依赖候选,目标主机的驱动、glibc 和 CUDA/EGL 仍需验证。 + ## 查看评估结果 示例指南提供了评估 40 轮任务(episode)的命令。结果写入 `results.json`, diff --git a/docs/zh/quickstart.md b/docs/zh/quickstart.md index 5495665..a00c9ef 100644 --- a/docs/zh/quickstart.md +++ b/docs/zh/quickstart.md @@ -11,6 +11,8 @@ uv sync --frozen ``` 依赖组和可选组件见[安装指南](installation.md)。 +如需一次生成已联动的可编辑演示配置,运行 +`uv run --frozen embodirun example init xlerobot`,再看[示例指南](examples.md)。 ## 1. 运行模拟设备 diff --git a/docs/zh/support-matrix.md b/docs/zh/support-matrix.md index 582d1bb..1cc1974 100644 --- a/docs/zh/support-matrix.md +++ b/docs/zh/support-matrix.md @@ -36,7 +36,7 @@ **◐ ARX5** — DM0.5 绑定。
**◐ Unitree Go2** — StreamVLN 导航。
- **◐ XLeRobot** — 通过独立硬件服务接入。 + **◐ XLeRobot** — 独立硬件服务;双臂 π0.5 Recipe。 [查看机器人适配器](https://github.com/BUAA-CI-LAB/EmbodiRun/tree/main/src/embodirun/robots) @@ -82,7 +82,7 @@ | Franka FR3 | π0.5 绑定 | [适配器](https://github.com/BUAA-CI-LAB/EmbodiRun/tree/main/src/embodirun/robots/franka/fr3) | | ARX5 | DM0.5 绑定 | [绑定](https://github.com/BUAA-CI-LAB/EmbodiRun/tree/main/src/embodirun/bindings/arx/x5/dm05) | | Unitree Go2 | StreamVLN 导航 | [机器人集成](https://github.com/BUAA-CI-LAB/EmbodiRun/tree/main/src/embodirun/robots/unitree/go2) | - | XLeRobot | 独立硬件服务 | [集成包](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/integrations/xlerobot_owner/README.md) | + | XLeRobot | 独立硬件服务;双臂使用 `lerobot.xlerobot.pi05` | [Recipe](xlerobot-snack-delivery.md) / [owner 集成包](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/integrations/xlerobot_owner/README.md) | | SO-101(有线遥操作) | 多主臂 UDP 分发,从臂侧 episode 采集 | [集成包](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/integrations/so101_wired_teleop/README.md) | SO-101 部署需要已标定的机械臂、相机映射,以及针对所选绑定 @@ -109,6 +109,39 @@ [WirelessComm 配置](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/configs/http-wireless-inference/wireless.yaml) 和[传输测量](inference-transport.md)。 +## MicroDuck Recipe 前提 {#microduck-prerequisites} + +[MicroDuck 指南](microduck-vln.md)分别描述软件与仿真流程。Native setup 与两个容器目标 +共用 Python 3.12 / uv 0.12.x Recipe 锁。软件 `init`、`validate`、`plan` 与 +`check --mode software` 无需 GPU、模型或场景。默认的 setup/check 模式是 simulation, +增加锁定的可选依赖;场景、checkpoint、episode、依赖导入、CUDA/EGL 与模型导航结果 +仍需在目标机器另行验证。完整 profile 仍为实验性;软件 CI 不构成新模型/硬件组合的验证。 + +## XLeRobot Recipe 前提 {#xlerobot-recipe-prerequisites} + +先从[软件演练](xlerobot-snack-delivery.md)开始。它使用 fixture,不需要下表中的实体前提。 +native setup 使用 Python 3.12 / uv 0.12.x;Docker `xlerobot-software` 服务需要 +Linux 上的 Docker Engine 与 Compose。native 软件/硬件 profile 与 XLeRobot 容器目标 +都使用 `examples/xlerobot_snack_delivery/uv.lock`。硬件 owner 使用 CPU PyTorch, +模型推理由独立服务承担。 + +| 输入 | 准备自己的小车时何时需要 | 软件演练使用什么 | +|---|---|---| +| owner 环境 / SDK | `setup --mode hardware` 安装 owner;将 `sdk_src` 指向诊断中 `environment.sdk_src` 给出的锁定 LeRobot 目录。自备 SDK checkout 需单独确认版本。 | 软件依赖,不导入 owner 或 SDK | +| 双臂 binding 与检查点 | 接入推理前,为 `lerobot.xlerobot.pi05` 准备适配 XLeRobot 的 π0.5 检查点。该 binding 控制双臂;底盘路线使用独立 Control scope。 | Fixture 提议,无需检查点 | +| 动作 feature names 与单位 | 调用模型前,提供左右各六个字段:`left_arm_` / `right_arm_` 加 `shoulder_pan.pos`、`shoulder_lift.pos`、`elbow_flex.pos`、`wrist_flex.pos`、`wrist_roll.pos`、`gripper.pos`。关节位置用 `degrees`,夹爪用 `range_0_100`。 | 具名 fixture 数值,不验证学习动作质量 | +| 相机映射 | 硬件检查前,在 deployment 中将 `observation.images.front`、`observation.images.left_wrist`、`observation.images.right_wrist` 映射到 owner 角色。推理前确认实际图像与检查点输入语义。 | 无真实相机 | +| 标定与设备路径 | owner 启动前,在 `hardware.local.json` 提供本机已验证的标定、稳定串口/相机路径、轮向/几何和限位。文件存在不代表标定有效。 | 允许模板占位值 | +| 路线 | `dry-run` 可用自带 `fixture: true` 路线。硬件准备需要本场地录制、带方向的非 fixture 片段;运动前验证每条路线。 | 零运动 fixture | +| Handover | 监督递送前,按本机标定与场地验证 `handover.forward_pose` / `gripper_opening`。 | Fixture 任务顺序 | +| 模型 endpoint | 启动前填写 `deployment.local.yaml` 的 `model.endpoint`;模型驱动运动前单独验证服务与具名输出。 | 不连接模型 | +| 真机停止 | 操作员在场,运动前和停止后取得 owner 的新鲜停止确认。停止请求被接受或轮速为零仍不足以确认。 | 模拟反馈 | + +`check --mode hardware --json` 只读本地文件和声明。退出 0 / `status: "passed"` +不验证设备连接、实际标定、相机图像、模型输出、新鲜观测、停止反馈或任务成功。 +`setup` / `check` 默认是 hardware,软件演练每次显式使用 `--mode software`。 +Linux ARM64 依赖与可选集成需在目标机器检查;仅有容器镜像不构成硬件/GPU 支持证明。 + ## 路线图 {#roadmap}
diff --git a/docs/zh/xlerobot-snack-delivery.md b/docs/zh/xlerobot-snack-delivery.md index 80d4fb8..78bd880 100644 --- a/docs/zh/xlerobot-snack-delivery.md +++ b/docs/zh/xlerobot-snack-delivery.md @@ -29,6 +29,8 @@ XLeRobot 沿录制路线前往取物台,通过 π0.5/VLA 生成抓取动作, | 接口与执行反馈 | [`architecture.md`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/architecture.md) | | 部署字段 | [`deployment.example.yaml`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/deployment.example.yaml)、[`config.example.json`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/config.example.json) | | 统一配置与启动 | [`example.yaml`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/example.yaml)、[`examples/run.sh`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/run.sh) | +| 让现有 coding Agent 帮助部署 | [`AGENT_GUIDE.md`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/AGENT_GUIDE.md) | +| 课堂试用与首次使用反馈 | [`FIRST_USE_FEEDBACK.md`](https://github.com/BUAA-CI-LAB/EmbodiRun/blob/main/examples/xlerobot_snack_delivery/FIRST_USE_FEEDBACK.md) | 默认路线文件是测试数据,仅供 `dry-run` 使用。 在真实场景中,先录制带方向的路线片段,也可让 RPent 根据路线图选择本地片段。 @@ -36,17 +38,47 @@ XLeRobot 沿录制路线前往取物台,通过 π0.5/VLA 生成抓取动作, ## 无硬件演练 -执行 `uv sync --frozen` 后,通过共享入口演练该任务: +选择 native 安装或 [Docker 软件路径](installation.md#managed-deployments)。 +两者都需要目标 Linux 主机上的 checkout 与首次安装的网络访问。 +native XLeRobot setup 使用 Python 3.12 与 uv 0.12.x: ```bash -bash examples/run.sh examples/xlerobot_snack_delivery/example.yaml validate -bash examples/run.sh examples/xlerobot_snack_delivery/example.yaml dry-run +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init xlerobot +CONFIG=examples/local/xlerobot/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" setup --mode software +uv run --frozen embodirun example "$CONFIG" check --mode software --json +uv run --frozen embodirun example "$CONFIG" dry-run ``` -示例 README 说明了本地 YAML 配置、服务启动、操作员监督下的执行和输出文件。 -常用命令见[复现演示](examples.md)。 +此路径无需真实 SDK、标定、相机、路线录制、检查点、model endpoint、RPent 或 GPU。 +`init` 遇到已有目录时,复用其提示的配置路径或另选 `--output`;已有工作不会覆盖。 +Docker 使用 `xlerobot-software`,镜像已有软件环境,跳过 native `setup`。 +它与 native XLeRobot setup 使用同一个 Recipe 依赖项目和锁。 -对于硬件,请遵循配置指南中的 owner 与安全说明, -保持操作员在场,并在允许运动前校验机器人特定的标定与 -急停。为你的工作空间录制路线,并标定机械臂与 -相机。 +`check` 通过时退出 0、`status: "passed"`,有待处理问题时退出 2。 +读取每个 issue 的 `location` 与 `next_action`,修复后重跑同一检查。 +终端打印的输出目录包含 `run.json`、`command-0.log` 和 +`result/status.json` / `result/events.jsonl`。完成的 dry-run 记录 fixture 任务顺序, +`task_success` 为 `unverified`、`physical_success` 为 null;不启动 owner、推理服务或任务 Agent。 + +## 准备自己的小车 + +继续使用生成的本地目录: + +| 文件 | 需要填写的本机信息 | +|---|---| +| `deployment.local.yaml` | 模型 endpoint、Control 端口、相机 feature 到 owner 角色的映射 | +| `hardware.local.json` | 已安装 SDK 路径、设备、已验证标定、轮参数、限位 | +| `config.local.json` 和 `routes/` | 录制的有向路线、抓取指令、规划器、标定后的递交姿态 | + +执行 `setup --mode hardware` 准备完整 owner 环境,再用 +`check --mode hardware --json` 检查本地前提。省略 `--mode` 时,两者默认是 hardware。 +双臂 `lerobot.xlerobot.pi05` binding、单位、相机和输入前提见 +[支持矩阵](support-matrix.md#xlerobot-recipe-prerequisites)。 + +静态检查不代表实际就绪。按照配置指南,由在场操作员验证标定、实际相机图像、 +模型适配和新鲜停止反馈,再授权运动。推理单独运行,安装 owner 不会提供检查点。 +监督下的 `up` / `run` 和关闭方式见局部 README。课堂反馈材料已提供,真实同学反馈仍待收集。 diff --git a/examples/README.md b/examples/README.md index f70d502..b00dca4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,16 +1,27 @@ # Runnable examples -Use one YAML manifest and one launcher for each demo. Run these commands from -the repository root after `uv sync --frozen`: +Use the installed `embodirun example` command for all recipes. `examples/run.sh` +uses the same Python runner for source checkouts. From the repository root: ```bash -bash examples/run.sh examples/multi_robot_serving/example.yaml validate -bash examples/run.sh examples/multi_robot_serving/example.yaml plan +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init xlerobot +uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml validate +uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml plan +uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml setup --mode software +uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml check --mode software --json +uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml dry-run ``` -Both commands are offline: they neither connect to devices nor start inference. -`validate` checks the configuration; `plan` prints the command, arguments, and -output directory that `run` would use. +`init` creates one new, ignored directory with all referenced files linked: +manifest, deployment, hardware configuration, task, and routes for XLeRobot. +It refuses to overwrite an existing directory and shows how to reuse an existing +manifest. Other templates are `so101`, +`embodiinfer-http`, `embodiinfer-wireless`, `sglang-http`, and `microduck`. +Use `--output NEW_DIRECTORY` to choose a location; MicroDuck accepts +`--assets ASSET_ROOT` to fill its project, checkpoint, and episode paths. +`validate` and `plan` are offline. `plan` prints the command and output path +without launching it. | Demo | Configuration | Setup and execution | |---|---|---| @@ -40,16 +51,24 @@ parameters: - `name`: a readable identifier for the example. - `kind`: `rollout`, `microduck`, or `snack`; selects an existing execution path. - `output_dir`: output root. Each invocation gets a fresh timestamped directory. -- `python` (optional): interpreter for MicroDuck or XLeRobot; defaults to the - launcher's interpreter. Host commands use the Host environment. +- `python` (optional): interpreter for MicroDuck or XLeRobot; the launcher + otherwise selects the dedicated recipe environment after `setup`. Host + commands use the Host environment. - `parameters`: settings for that execution path, shown in the shipped manifests. Unknown fields, duplicate YAML keys, and invalid numeric limits are rejected. +An explicit manifest `python` takes priority over `EMBODIRUN_SCENE_PYTHON`, +including the interpreter configured by a Docker image. Before moving a native +manifest into Docker, back it up and remove only the Docker copy's top-level +`python` override to use the image's `/opt/venv/bin/python`; preserve other +settings and calibration. Alternatively, initialize a separate Docker directory +and migrate your local configuration with its path references. + Manifest paths resolve **relative to the YAML file**, independent of the shell's working directory. Paths inside a deployment remain paths on the corresponding -node. There is no shell interpolation in YAML; write concrete values. Copy a -manifest and deployment to `*.local.yaml` in the same directory before editing; -local YAML/JSON files are ignored by Git. +node. There is no shell interpolation in YAML; write concrete values. Use +`init` to create and link editable local files; `examples/local/` is ignored by +Git. Deployment YAML owns nodes, devices, cameras, models, and bindings. The example manifest owns the task and execution limits. XLeRobot references its existing @@ -61,14 +80,22 @@ and episode manifest. | Command | SO-101 rollout | XLeRobot | MicroDuck | |---|---|---|---| | `validate` / `plan` | Offline configuration / command preview | Same | Same; assets need not be installed | -| `setup` | Host `init`, then sync this checkout | Install the example and owner environment | Follow the dedicated environment instructions | +| `setup` | Host `init`, then sync this checkout | `--mode software` for the small runtime; `--mode hardware` for full owner, both from the Recipe lock | `--mode software` for CLI/integration checks; default `simulation` adds its locked simulator/inference extra | | `up --allow-hardware` | Start model and robot services | Start owner and Control in the foreground | Inference starts with `run` | | `run` | Concurrent bounded rollouts; requires `--allow-hardware` | Supervised delivery; requires `--allow-hardware` | Launch simulation and inference | | `dry-run` | — | Fixture-only task rehearsal | — | -| `check` | — | — | Check assets, CUDA, EGL, controller, and encoder | -| `down` | Stop this deployment | Ctrl-C in the `up` terminal | Child services stop when `run` exits | - -Hardware examples require calibrated devices, a working emergency stop, and an +| `check` | Report local placeholders and node-owned prerequisites | Read-only diagnostics for `--mode software` or `--mode hardware`; `--json` includes issues and next actions | Software mode reads configuration/metadata; default simulation checks resources and runs scene preflight without `--json` | +| `down` | Stop this deployment | Ask this recipe's foreground launcher to stop | Ask this recipe's foreground launcher to stop | + +XLeRobot `setup` / `check` default to hardware mode when `--mode` is omitted. +Use `--mode software` explicitly for the first rehearsal. Its `check --json` +exits 0 with `status: "passed"` or 2 with `needs_attention`; resolve the listed +`location` / `next_action` and repeat the same check. The report separates +`verified` from `unverified` so its scope remains visible. + +`check` is a prerequisite check, not proof that a robot, model, camera, or stop +feedback is ready. The MicroDuck scene preflight uses GPU resources; run it on +the target Linux GPU host. Hardware examples require calibrated devices, a working emergency stop, and an operator. `--allow-hardware` permits startup or motion; it does not skip action limits or XLeRobot's confirmation gates. Stop one deployment before starting another that uses the same robot or ports. @@ -82,15 +109,41 @@ to `command-0.log`, `command-1.log`, etc. XLeRobot's hardware prompts and servic startup stay in the terminal. MicroDuck and XLeRobot write detailed results under `result/`; their guides describe those files. +If bootstrap `uv sync` or `docker compose build` fails before this launcher +starts, there is no Recipe `Outputs:` / `run.json` yet. Save that command's +terminal stdout/stderr to a chosen log file and record its command and exit code. + SO-101 services remain running after a successful rollout so you can reset the scene and repeat it. Use `down` when finished. On a rollout failure or interrupt, the launcher stops the deployment, not just the local HTTP clients. Inspect the robot and use the physical emergency stop if shutdown cannot be confirmed. Do not run unrelated work in the example's deployment. +Docker services share a Compose-project `/tmp` named volume for private launcher +sockets. Container startup and `down` must use the same Compose project, +host UID/GID, and configuration so a second container can reach the launcher. +The volume shares launcher IPC, not dependency caches; retain the private +permissions and wait for cleanup confirmation. Hardware stop still needs +physical feedback. + Set `EMBODIRUN_EXAMPLE_PYTHON` to an absolute Host Python path if you are not using the repository's `.venv`. +For XLeRobot, the separate environment uses +`examples/xlerobot_snack_delivery/pyproject.toml` and `uv.lock` for both software +and full hardware setup. The full owner installs CPU PyTorch; the GPU model +service is prepared separately. The root checkout Host environment includes +the default development group. MicroDuck uses its own Recipe project and lock +under `examples/microduck_vln/` for software and simulation modes. +Do not copy an existing machine's virtual environment. + +Container targets `host`, `xlerobot-software`, `xlerobot`, `microduck-software`, and `microduck` are in +the root `Dockerfile`. XLeRobot native and container profiles use the same +Recipe lock; the software container already has its environment. See the +[XLeRobot README](xlerobot_snack_delivery/README.md) for copyable Linux software +commands, and the [installation guide](../docs/en/installation.md) for opt-in +hardware/GPU profiles. Building an image does not enable robot motion. + ## Smaller software examples - [Simulated device and public Control API](shared-device-fake.md) @@ -98,3 +151,13 @@ using the repository's `.venv`. For latency measurements and report comparison, use [benchmarks](../benchmarks/README.md). + +## First-use feedback + +Use the [classroom trial and feedback template](xlerobot_snack_delivery/FIRST_USE_FEEDBACK.md) +for a first software-only trial. The [deployment Agent guide](xlerobot_snack_delivery/AGENT_GUIDE.md) +provides a starting prompt for your existing coding Agent. Record where the +entrypoint was unclear, which local file you needed to edit, what a successful +software run meant, and which missing paths or real information required help. +Actual student feedback has not been collected. Independent Agent rehearsal, +classroom feedback, model validation, and physical delivery are separate results. diff --git a/examples/configuration.py b/examples/configuration.py new file mode 100644 index 0000000..cc70335 --- /dev/null +++ b/examples/configuration.py @@ -0,0 +1,444 @@ +"""Prepare linked local recipe files and report missing deployment inputs.""" + +from __future__ import annotations + +import argparse +import json +import os +import shlex +import shutil +import subprocess +import sys +from pathlib import Path +from typing import Any + +import yaml + +from embodirun.deployment.config.loader import _load_yaml +from examples.setup_environment import recipe_mode + +ROOT = Path(__file__).resolve().parents[1] +TEMPLATES = { + "so101": "multi_robot_serving/example.yaml", + "embodiinfer-http": "engine_comparison/embodiinfer-http.yaml", + "embodiinfer-wireless": "engine_comparison/embodiinfer-wireless.yaml", + "sglang-http": "engine_comparison/sglang-http.yaml", + "xlerobot": "xlerobot_snack_delivery/example.yaml", + "microduck": "microduck_vln/example.yaml", +} + + +def initialize(template: str, destination: Path, *, assets: Path | None = None) -> Path: + """Copy a complete editable recipe without overwriting an existing directory.""" + source = ROOT / "examples" / TEMPLATES[template] + data = _load_yaml(source) + p = data["parameters"] + files: dict[Path, bytes] = {} + data.pop("python", None) + data["output_dir"] = "runs" + if data["kind"] == "rollout": + files[Path("deployment.local.yaml")] = (source.parent / p["deployment"]).read_bytes() + p["deployment"] = "deployment.local.yaml" + elif data["kind"] == "snack": + task = json.loads((source.parent / p["config"]).read_text()) + task["control"].pop("endpoints", None) + for name, route in task["navigation"]["routes"].items(): + target = Path("routes") / f"{name}.local.json" + files[target] = (source.parent / route).read_bytes() + task["navigation"]["routes"][name] = target.as_posix() + deployment = _load_yaml(source.parent / p["deployment"]) + hardware = source.parent / deployment["owner"]["hardware_config"] + files[Path("hardware.local.json")] = hardware.read_bytes() + deployment["owner"]["hardware_config"] = "hardware.local.json" + files[Path("deployment.local.yaml")] = yaml.safe_dump(deployment, sort_keys=False).encode() + files[Path("config.local.json")] = (json.dumps(task, indent=2) + "\n").encode() + p.update(config="config.local.json", deployment="deployment.local.yaml") + else: + files[Path("assets.local.json")] = (source.parent / p["manifest"]).read_bytes() + p["manifest"] = "assets.local.json" + p["inference_root"] = str(ROOT / "third_party/embodiinfer") + if assets is not None: + assets = assets.expanduser().resolve() + p.update( + project_root=str(assets), + checkpoint=str(assets / "lightnav_sft_v3/merged"), + episodes=str(assets / "data/demo_microduck_vln.jsonl"), + ) + if assets is not None and data["kind"] != "microduck": + raise ValueError("--assets is only used by the microduck template") + files[Path("example.local.yaml")] = yaml.safe_dump(data, sort_keys=False).encode() + destination = destination.expanduser().resolve() + # Exclusive directory creation protects an existing calibration or edited task. + destination.mkdir(parents=True, exist_ok=False) + for name, content in files.items(): + target = destination / name + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(content) + return destination / "example.local.yaml" + + +def init_main(argv: list[str]) -> int: + parser = argparse.ArgumentParser( + description="Create a complete local recipe with its file references already connected." + ) + parser.add_argument("template", choices=TEMPLATES) + parser.add_argument("--output", type=Path, help="new directory (default: examples/local/TEMPLATE)") + parser.add_argument("--assets", type=Path, help="MicroDuck asset root; derives checkpoint and episode paths") + args = parser.parse_args(argv) + destination = (args.output or ROOT / "examples/local" / args.template).expanduser().resolve() + try: + path = initialize(args.template, destination, assets=args.assets) + except FileExistsError: + print(f"Recipe directory already exists: {destination}. Existing files were preserved.", file=sys.stderr) + existing = destination / "example.local.yaml" + if existing.is_file(): + print(f"Next: {_recipe_command(existing, 'validate')}", file=sys.stderr) + else: + print("Next: choose a new directory with init --output DIR.", file=sys.stderr) + return 2 + print(f"Stage: recipe files created\nManifest: {path}\nNext: {_recipe_command(path, 'validate')}") + if args.template == "xlerobot": + print(f"Install rehearsal environment: {_recipe_command(path, 'setup', '--mode', 'software')}") + print(f"Check rehearsal inputs: {_recipe_command(path, 'check', '--mode', 'software', '--json')}") + print(f"Software rehearsal: {_recipe_command(path, 'dry-run')}") + elif args.template == "microduck": + print(f"Install software environment: {_recipe_command(path, 'setup', '--mode', 'software')}") + print(f"Check software inputs: {_recipe_command(path, 'check', '--mode', 'software', '--json')}") + print("Software checks do not require scene assets or a GPU; simulation setup/check is separate.") + else: + print(f"Next after validation: {_recipe_command(path, 'check')}") + return 0 + + +def placeholders(value: Any, location: str = "") -> list[str]: + """Find unresolved template values, preserving the field names in diagnostics.""" + if isinstance(value, dict): + return [item for key, child in value.items() for item in placeholders(child, f"{location}.{key}".lstrip("."))] + if isinstance(value, list): + return [item for index, child in enumerate(value) for item in placeholders(child, f"{location}[{index}]")] + if isinstance(value, str) and ("REPLACE_" in value or value.startswith(("/path/to/", "/absolute/path/to/"))): + return [f"{location}: replace {value}"] + return [] + + +def _recipe_command(path: Path, *args: str) -> str: + entrypoint = Path(sys.executable).absolute().with_name("embodirun") + environment = [f"EMBODIRUN_SOURCE_ROOT={ROOT}"] + if scene_python := os.environ.get("EMBODIRUN_SCENE_PYTHON"): + environment.append(f"EMBODIRUN_SCENE_PYTHON={scene_python}") + command = ( + [str(entrypoint)] if entrypoint.is_file() else ["uv", "run", "--frozen", "--project", str(ROOT), "embodirun"] + ) + return shlex.join(["env", *environment, *command, "example", str(path), *args]) + + +def _environment_details(python: str, packages: list[str]) -> dict[str, Any]: + """Read metadata in the selected interpreter; do not import hardware or ML code.""" + script = """ +import importlib.metadata, json, pathlib, sys +versions = {} +for name in json.loads(sys.argv[1]): + try: + versions[name] = importlib.metadata.version(name) + except importlib.metadata.PackageNotFoundError: + versions[name] = None +try: + sdk_src = str(pathlib.Path(importlib.metadata.distribution('lerobot').locate_file('lerobot')).parent) +except importlib.metadata.PackageNotFoundError: + sdk_src = None +print(json.dumps({'version': list(sys.version_info[:3]), 'packages': versions, 'sdk_src': sdk_src})) +""" + result = subprocess.run( + [python, "-c", script, json.dumps(packages)], capture_output=True, text=True, check=True, timeout=15 + ) + return json.loads(result.stdout) + + +def check_report(example: Any, *, mode: str | None = None) -> dict[str, Any]: + """Check installed metadata and local inputs without connecting to services/devices.""" + mode = recipe_mode(example.kind, mode) + p = example.parameters + issues: list[dict[str, str]] = [] + report: dict[str, Any] = { + "recipe": str(example.path), + "kind": example.kind, + "mode": mode, + "status": "passed", + "issues": issues, + "verified": ["Manifest and task configuration structure"], + "unverified": ["Live model/service availability"], + "next_actions": [], + } + if example.kind != "microduck": + report["unverified"].append("Physical device connection, calibration and stop feedback") + + def issue(code: str, location: str, message: str, action: str) -> None: + issues.append({"code": code, "location": location, "message": message, "next_action": action}) + + def unresolved(value: Any, location: str) -> None: + for problem in placeholders(value, location): + field, _, value_text = problem.partition(": replace ") + issue("placeholder", field, f"replace {value_text}", f"Edit {field} with the value for this deployment.") + + setup = ( + _recipe_command(example.path, "setup", "--mode", mode) + if example.kind == "snack" or (example.kind == "microduck" and mode == "software") + else _recipe_command(example.path, "setup") + ) + report["environment"] = {"python": example.python} + if not Path(example.python).is_file() or shutil.which(example.python) is None: + issue("python_missing", "python", f"Python is unavailable: {example.python}", setup) + elif example.kind in {"snack", "microduck"}: + try: + report["environment"]["path"] = str(example.environment) + except ValueError as error: + issue( + "python_environment", + "python", + str(error), + f"Correct python in {example.path} or EMBODIRUN_SCENE_PYTHON to name a venv/bin/python, then repeat setup.", + ) + packages = ["embodirun", "PyYAML", "paramiko", "rich"] + if example.kind == "microduck": + packages.append("embodirun-microduck") + if mode == "simulation": + packages += [ + "numpy", + "Pillow", + "mujoco", + "onnxruntime", + "casadi", + "imageio-ffmpeg", + "torch", + "torchvision", + "transformers", + "tokenizers", + "huggingface-hub", + "safetensors", + "einops", + "accelerate", + ] + elif mode == "hardware": + packages += [ + "embodirun-xlerobot-owner", + "aiohttp", + "cryptography", + "lerobot", + "opencv-python-headless", + "numpy", + "Pillow", + "torch", + "torchvision", + "datasets", + "pandas", + "pyarrow", + "av", + "jsonlines", + "feetech-servo-sdk", + "pyserial", + "deepdiff", + ] + try: + details = _environment_details(example.python, packages) + report["environment"].update(details) + if details["version"][:2] != [3, 12]: + name = "MicroDuck" if example.kind == "microduck" else "XLeRobot" + issue("python_version", "python", f"The {name} recipe uses Python 3.12.", setup) + missing = [name for name, version in details["packages"].items() if version is None] + if missing: + issue("dependencies_missing", "python", f"Missing installed packages: {', '.join(missing)}", setup) + else: + report["verified"].append(f"Installed {mode} package metadata in the selected Python") + except (OSError, ValueError, subprocess.SubprocessError) as error: + issue("environment_inspection", "python", f"Cannot inspect this Python: {error}", setup) + report["unverified"].append("Optional dependency imports and external SDK compatibility") + if example.kind == "rollout": + path = example.resolve(p["deployment"]) + unresolved(_load_yaml(path), str(path)) + report["unverified"].append("SSH host keys, device paths and checkpoint features on the configured nodes") + elif example.kind == "snack": + from examples.xlerobot_snack_delivery.run import load_recipe_config + + deployment_path = example.resolve(p["deployment"]) + deployment = _load_yaml(deployment_path) + task_path = example.resolve(p["config"]) + task = load_recipe_config(task_path, deployment_path) + report["control_endpoints"] = task["control"]["endpoints"] + report["verified"].append("Scoped local Control addresses derived from the deployment") + task_values = dict(task) + task_values["planning"] = dict(task.get("planning", {})) + if task_values["planning"].get("mode", "recorded_route") != "diagram": + task_values["planning"].pop("route_diagram", None) + unresolved(task_values, str(task_path)) + for name, value in task["navigation"]["routes"].items(): + location = f"{task_path}.navigation.routes.{name}" + action = f"Record and verify a room route, then edit {location} to point to that JSON." + if not isinstance(value, str): + if mode == "hardware" and isinstance(value, dict) and value.get("fixture") is True: + issue("fixture_route", location, "route is a software fixture", action) + continue + route_path = (task_path.parent / value).resolve() + if not route_path.is_file(): + issue("route_missing", location, f"route does not exist: {route_path}", f"Correct {location}.") + else: + route = json.loads(route_path.read_text()) + if not isinstance(route, dict): + issue("route_invalid", str(route_path), "route JSON must be an object", f"Correct {route_path}.") + elif mode == "hardware" and route.get("fixture") is True: + issue("fixture_route", str(route_path), "software fixture; not a hardware route", action) + if not any(item["code"] in {"route_missing", "route_invalid"} for item in issues): + report["verified"].append("Local route file availability and JSON syntax") + report["unverified"] += [ + "Recorded route execution, handover pose and task outcome", + "Two-arm checkpoint feature names, units and camera inputs for lerobot.xlerobot.pi05", + ] + if mode == "software": + report["unverified"].append("Owner SDK, serial devices and cameras (unused by software rehearsal)") + else: + unresolved(deployment, str(deployment_path)) + hardware_path = (deployment_path.parent / deployment["owner"]["hardware_config"]).resolve() + if not hardware_path.is_file(): + issue( + "hardware_config_missing", + str(hardware_path), + "owner hardware configuration is missing", + f"Correct {deployment_path}.owner.hardware_config to point to your hardware JSON.", + ) + else: + hardware = json.loads(hardware_path.read_text()) + if not isinstance(hardware, dict): + raise ValueError(f"{hardware_path} must contain a JSON object") + for field in ("ports", "cameras"): + if not isinstance(hardware.get(field, {}), dict): + raise ValueError(f"{hardware_path}.{field} must be an object") + unresolved( + {k: v for k, v in hardware.items() if k not in {"sdk_src", "calibration"}}, str(hardware_path) + ) + sdk = hardware.get("sdk_src") + recommended = report["environment"].get("sdk_src") + sdk_action = ( + f"Set {hardware_path}.sdk_src to {recommended} to use this environment's locked LeRobot." + if recommended + else f"Run {setup}, then set {hardware_path}.sdk_src to its reported SDK directory." + ) + if not isinstance(sdk, str) or placeholders(sdk) or not Path(sdk).expanduser().is_dir(): + issue( + "sdk_missing", f"{hardware_path}.sdk_src", f"SDK source directory is missing: {sdk}", sdk_action + ) + elif not any( + (Path(sdk).expanduser() / item).is_file() + for item in ("lerobot/motors/feetech/__init__.py", "lerobot/motors/feetech.py") + ): + issue( + "sdk_layout", + f"{hardware_path}.sdk_src", + "SDK path must contain lerobot/motors/feetech", + sdk_action, + ) + elif not recommended or Path(sdk).expanduser().resolve() != Path(recommended).resolve(): + report["unverified"].append( + "The explicit external sdk_src version is not guaranteed by the recipe lock" + ) + calibration = hardware.get("calibration") + if calibration is None or ( + isinstance(calibration, str) + and (placeholders(calibration) or not Path(calibration).expanduser().is_file()) + ): + issue( + "calibration_missing", + f"{hardware_path}.calibration", + f"calibration is missing: {calibration}", + f"Supply verified motor calibration in {hardware_path}.calibration before hardware use.", + ) + if not hardware.get("camera_roles_confirmed"): + issue( + "camera_roles_unconfirmed", + f"{hardware_path}.camera_roles_confirmed", + "camera_roles_confirmed is false; verify front/left_wrist/right_wrist mappings", + f"Verify each physical camera role, then update {hardware_path}.camera_roles_confirmed.", + ) + for role, source in hardware.get("ports", {}).items(): + if isinstance(source, str) and not placeholders(source) and not Path(source).expanduser().exists(): + issue( + "serial_path_missing", + f"{hardware_path}.ports.{role}", + f"serial path does not exist: {source}", + f"Inspect device enumeration and correct {hardware_path}.ports.{role}.", + ) + cameras = hardware.get("cameras", {}) + for feature, role in deployment.get("cameras", {}).items(): + if role not in cameras: + issue( + "camera_role_missing", + f"{deployment_path}.cameras.{feature}", + f"missing hardware role {role}", + f"Connect {deployment_path}.cameras.{feature} to a role in {hardware_path}.cameras.", + ) + elif cameras[role] is None: + issue( + "camera_source_missing", + f"{hardware_path}.cameras.{role}", + f"camera role {role} has no source", + f"Set {hardware_path}.cameras.{role} to the verified device index, path or stream.", + ) + elif ( + isinstance(cameras[role], str) + and "://" not in cameras[role] + and not placeholders(cameras[role]) + and not Path(cameras[role]).expanduser().exists() + ): + issue( + "camera_path_missing", + f"{hardware_path}.cameras.{role}", + f"camera role {role} does not exist: {cameras[role]}", + f"Inspect camera enumeration and correct {hardware_path}.cameras.{role}.", + ) + else: + report["unverified"] += [ + "CUDA/EGL availability and MicroDuck scene/model preflight", + "External scene/checkpoint/episode assets and inference source compatibility", + "Learned-model inference and navigation success", + ] + if mode == "simulation": + for key in ("project_root", "inference_root", "checkpoint", "episodes", "manifest"): + path = example.resolve(p[key]) + if not path.exists(): + issue( + "asset_missing", + f"{example.path}.parameters.{key}", + f"asset does not exist: {path}", + f"Supply this asset and correct {example.path}.parameters.{key}.", + ) + inference_root = example.resolve(p["inference_root"]) + if inference_root.exists() and not (inference_root / "embodiinfer/__init__.py").is_file(): + initialize_source = shlex.join( + ["git", "-C", str(ROOT), "submodule", "update", "--init", "third_party/embodiinfer"] + ) + issue( + "inference_source_missing", + f"{example.path}.parameters.inference_root", + f"EmbodiInfer entrypoint is missing: {inference_root / 'embodiinfer/__init__.py'}", + f"Initialize the pinned source with {initialize_source}, or select a compatible inference_root.", + ) + report["status"] = "needs_attention" if issues else "passed" + report["next_actions"] = list(dict.fromkeys(item["next_action"] for item in issues)) + if not issues: + if example.kind == "microduck" and mode == "software": + report["next_actions"] = [ + "For a simulation run, supply external assets and the pinned inference source, then use " + + _recipe_command(example.path, "setup", "--mode", "simulation") + ] + else: + report["next_actions"] = [ + _recipe_command(example.path, "dry-run") + if example.kind == "snack" and mode == "software" + else _recipe_command(example.path, "check", "--mode", "simulation") + if example.kind == "microduck" + else "Review the unverified device/model prerequisites with the operator before hardware startup." + ] + return report + + +def check_requirements(example: Any) -> list[str]: + """Compatibility view of the local hardware prerequisites.""" + return [f"{item['location']}: {item['message']}" for item in check_report(example)["issues"]] diff --git a/examples/lifecycle.py b/examples/lifecycle.py new file mode 100644 index 0000000..d298781 --- /dev/null +++ b/examples/lifecycle.py @@ -0,0 +1,128 @@ +"""Stop a foreground recipe through its own private socket, never through a saved PID.""" + +from __future__ import annotations + +import fcntl +import json +import os +import signal +import socket +import tempfile +import threading +from pathlib import Path +from types import TracebackType +from typing import Any + + +class Supervisor: + """Hold one action's lock while its children and their cleanup are alive.""" + + def __init__(self, output: Path, action: str) -> None: + self.output = output + self.action = action + self.cancelled = threading.Event() + self.finished = threading.Event() + self.pointer = output / f".launcher-{action}.json" + self.lock: Any = None + self.server: socket.socket | None = None + self.directory: tempfile.TemporaryDirectory | None = None + self.thread: threading.Thread | None = None + self.previous_signal: Any = None + + def __enter__(self) -> Supervisor: + self.output.mkdir(parents=True, exist_ok=True, mode=0o700) + self.lock = (self.output / f".launcher-{self.action}.lock").open("a+") + try: + fcntl.flock(self.lock, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError as error: + self.lock.close() + raise RuntimeError(f"{self.action} is already running for {self.output}; use down first") from error + try: + # A short temporary path also works with macOS's small Unix-socket path limit. + self.directory = tempfile.TemporaryDirectory(prefix="embodirun-", dir="/tmp") + endpoint = Path(self.directory.name) / "control.sock" + self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.server.bind(str(endpoint)) + os.chmod(endpoint, 0o600) + self.server.listen(1) + self.server.settimeout(0.2) + fd, temporary = tempfile.mkstemp(prefix=".launcher-", dir=self.output) + with os.fdopen(fd, "w") as stream: + json.dump({"socket": str(endpoint)}, stream) + os.replace(temporary, self.pointer) + self.previous_signal = signal.signal(signal.SIGTERM, self._terminate) + self.thread = threading.Thread(target=self._listen, daemon=True) + self.thread.start() + return self + except BaseException: + self.__exit__(None, None, None) + raise + + def _terminate(self, signum: int, frame: Any) -> None: + self.cancelled.set() + + def _listen(self) -> None: + assert self.server is not None + while not self.finished.is_set(): + try: + connection, _ = self.server.accept() + except TimeoutError: + continue + except OSError: + return + with connection: + connection.settimeout(2) + try: + if connection.recv(16) != b"stop\n": + continue + self.cancelled.set() + # Acknowledgement follows cleanup, not just receipt of the request. + if self.finished.wait(300): + connection.sendall(b"exited\n") + else: + connection.sendall(b"cleanup pending\n") + except OSError: + pass + + def __exit__(self, exc_type: type | None, exc: BaseException | None, tb: TracebackType | None) -> None: + self.finished.set() + if self.thread is not None: + self.thread.join(timeout=2) + if self.previous_signal is not None: + signal.signal(signal.SIGTERM, self.previous_signal) + if self.server is not None: + self.server.close() + self.pointer.unlink(missing_ok=True) + if self.directory is not None: + self.directory.cleanup() + if self.lock is not None: + self.lock.close() + + +def stop_owned(output: Path) -> None: + """Stop the task before its service owner; refuse stale/unknown process state.""" + stopped = False + errors: list[str] = [] + for action in ("run", "up", "check", "dry-run"): + pointer = output / f".launcher-{action}.json" + if not pointer.exists(): + continue + try: + endpoint = json.loads(pointer.read_text())["socket"] + if not isinstance(endpoint, str): + raise ValueError("launcher socket path must be a string") + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client: + client.settimeout(305) + client.connect(endpoint) + client.sendall(b"stop\n") + if client.recv(32) != b"exited\n": + raise RuntimeError("launcher did not confirm process cleanup") + except (OSError, ValueError, KeyError, TypeError, RuntimeError) as error: + errors.append(f"Cannot confirm {action} cleanup using {pointer}: {error}") + continue + print(f"{action}: launcher finished handling the stop request") + stopped = True + if errors: + raise RuntimeError("; ".join(errors)) + if not stopped: + print("No active local launcher was found. Inspect service logs and physical stop feedback at the owner.") diff --git a/examples/microduck_vln/README.md b/examples/microduck_vln/README.md index 350ef07..0b1fc02 100644 --- a/examples/microduck_vln/README.md +++ b/examples/microduck_vln/README.md @@ -21,15 +21,17 @@ The service binds an OS-assigned loopback port with a per-run credential. ## Prerequisites -- Linux, Bash, a CUDA GPU and working NVIDIA/EGL drivers. The previous simulation +- Offline configuration checks need Linux, Bash, Python 3.12 and uv 0.12.x. + The software profile needs no GPU, simulator, inference source, assets or checkpoint. +- Simulation additionally needs a CUDA GPU and working NVIDIA/EGL drivers. The previous simulation profile used one A800 80 GB GPU and four CPU cores; no physical robot is involved. -- A dedicated Python environment. Python 3.12 is the reference profile; optional - package metadata supports Python 3.10+ where its dependencies are available. -- EmbodiInfer at this repository's pinned `third_party/embodiinfer` submodule revision. -- A separately provisioned MicroDuck asset project and SFT-v3 merged checkpoint. +- A dedicated Python 3.12 environment for the native Recipe; the Docker images + include their own environment. +- For simulation, EmbodiInfer at this repository's pinned `third_party/embodiinfer` submodule revision. +- For simulation, a separately provisioned MicroDuck asset project and SFT-v3 merged checkpoint. Assets, checkpoints, datasets and recorded media are not included here. -Initialize the public inference submodule from the repository root: +Before simulation or building the full image, initialize the public inference submodule from the repository root: ```bash git submodule update --init third_party/embodiinfer @@ -66,42 +68,197 @@ creates no copies and never modifies the training adapter. ## Install and configure -From the EmbodiRun root, prepare a dedicated environment with package access: +The Recipe has its own [`pyproject.toml`](pyproject.toml) and +[`uv.lock`](uv.lock), using Python 3.12 and uv 0.12.x. Native `setup` and Docker +use this same lock. From the EmbodiRun root, prepare the Host CLI, create linked +local files and install the software profile: ```bash -python3.12 -m venv .venv-microduck -source .venv-microduck/bin/activate -python -m pip install -e . -python -m pip install -e './integrations/microduck_vln[full,test]' -cp examples/microduck_vln/example.yaml examples/microduck_vln/example.local.yaml +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init microduck +CONFIG=examples/local/microduck/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" setup --mode software +uv run --frozen embodirun example "$CONFIG" check --mode software --json ``` -Edit `example.local.yaml`: set `python` to the dedicated environment and -`parameters.project_root`, `checkpoint`, `episodes`, and `manifest` to your -assets. Paths resolve relative to the YAML file. The inference source defaults -to the pinned submodule; use `parameters.inference_root` for another compatible -checkout. It is added only to the inference child's import path. +The Host CLI uses the root lock; `setup` installs the separate MicroDuck environment +at `.venv-microduck`. Software mode installs Host's CLI dependencies and the base +MicroDuck integration, without PyTorch or MuJoCo. `check --mode software`, with or +without `--json`, checks Python 3.12, package presence and configuration, and +reports installed package versions. Version selection comes from frozen setup; +this metadata check does not compare every package against `uv.lock`. +It does not start a launcher, simulator or inference +process. Scene inputs and their compatibility remain in `unverified`; missing +scene paths do not fail this software check. This flow does not run a simulation rehearsal. + +The software flow was verified on Ubuntu 24.04 aarch64: native Python 3.12.3 +and container Python 3.12.14 installed the same 14 locked packages, and +`embodirun` / `run.sh` returned matching software reports. Both use Python 3.12; +their patch versions differ. Native verification used freshly downloaded wheel +assistance after the first uncached online setup reached an external 180-second +test limit (exit 130). The container flow passed with the production Dockerfile +on a warm retry (exit 0), after its new-builder, empty-cache build reached an +external 600-second test limit (exit 130). These results do not establish fast +uncached installation or independent first use. + +The separate Recipe software workflow runs for relevant source, dependency or +container changes, and supports manual dispatch. It checks the software scope with the production native installer +and Dockerfile. It runs `init`, `validate`, `plan` and software checks through +both entrypoints, copies printed commands outside the checkout without inherited +Recipe variables, checks caller-owned files and compares locked package inventories. +To repeat the check after software setup, select the installed Recipe Python: + +```bash +"$RECIPE_ENV/bin/python" scripts/check_recipe_software.py --recipe microduck +``` + +This checks software onboarding. Full simulation/model execution, native LeRobot +deployment comparisons and independent human first use remain follow-up work. + +`init` copies the reference inventory; `--assets /absolute/asset/root` fills +project, checkpoint and episode paths but does not check that those external +inputs exist. Without that option, edit the generated placeholders before +simulation. If the local recipe directory already exists, reuse it or choose +a new directory with `init --output`; existing edits are preserved. + +From the checkout root on the Linux GPU host, add the full simulator and +inference dependencies from the same lock before running the scene. Set the +manifest path again in a new terminal; if you chose a custom directory, +replace it with the original path: + +```bash +CONFIG=examples/local/microduck/example.local.yaml +uv run --frozen embodirun example "$CONFIG" setup --mode simulation +``` + +MicroDuck `setup` and `check` default to `simulation` when mode is omitted; +choose `software` explicitly for offline checks. Both setup modes call +`uv sync --frozen --no-default-groups` on this Recipe project, with +`--extra simulation` only for the full profile. The `simulation` extra includes +`embodirun-microduck[full]`. Its resolved +profile includes NumPy 1.26.4, Pillow 12.3.0, MuJoCo 3.8.1, ONNX Runtime 1.30.0, +CasADi 3.7.2 and Transformers 4.51.3. On Linux, PyTorch 2.10.0 and torchvision +0.25.0 use the official CUDA 13.0 wheel index. Those two versions align with +the pinned EmbodiInfer development/runtime lock; they are a candidate profile +for this Recipe, not proof of GPU compatibility or a restriction on every +EmbodiInfer backend. A fresh native full-profile installation did not finish +within an external 600-second validation limit (interrupted, exit 130); +package imports and CUDA/EGL probes were not run. The full Docker target uses +this lock, but its build was not executed in this validation round. Full +target-host installation, scene and model execution remain unverified. + +The launcher defaults to the repository-root `.venv-microduck/bin/python`. +If you install into another directory, set `EMBODIRUN_SCENE_PYTHON` to its +absolute `bin/python` path, or set `python` in your local example YAML. An +explicit YAML `python` takes precedence; keep it consistent with the selected +environment. The commands below use the Recipe's locked CLI environment. Edit +`example.local.yaml` if your asset layout differs. The inference source +defaults to the pinned submodule; use `parameters.inference_root` for another +compatible checkout. It is added only to the inference child's import path. The model profile requires `transformers==4.51.3`; keep it separate from -Transformers 5.x profiles. Provision packages and weights before running on an -offline cluster. The launcher does not install packages or download weights. +Transformers 5.x and native LeRobot profiles. Provision packages and weights +before running on an offline cluster. The sync commands install packages, but +do not download weights or external assets. + +For `examples/run.sh`, select the already-installed Host CLI with +`EMBODIRUN_EXAMPLE_PYTHON="$PWD/.venv/bin/python"`; it accepts the same manifest, +command and mode as `embodirun example`. For example: + +```bash +CONFIG=examples/local/microduck/example.local.yaml +EMBODIRUN_EXAMPLE_PYTHON="$PWD/.venv/bin/python" \ + examples/run.sh "$CONFIG" check --mode software --json +``` + +Independent first use and full inference remain separate acceptance checks; +neither dependency installation nor a Docker build proves them. + +## Docker + +Docker Engine, Compose and Buildx with BuildKit are required; check +`docker buildx version` and `docker compose version` before building. + +Build and use the software image on the target Linux host. It installs the same +software lock during the image build; no native Recipe environment is needed: + +```bash +docker compose build microduck-software +mkdir -p examples/local +docker compose run --rm --user "$(id -u):$(id -g)" microduck-software init microduck --output /workspace/microduck +docker compose run --rm --user "$(id -u):$(id -g)" microduck-software /workspace/microduck/example.local.yaml validate +docker compose run --rm --user "$(id -u):$(id -g)" microduck-software /workspace/microduck/example.local.yaml plan +docker compose run --rm --user "$(id -u):$(id -g)" microduck-software /workspace/microduck/example.local.yaml check --mode software --json +``` + +`examples/local/microduck` stays editable by your host UID/GID. Skip `setup` +inside the image: dependencies already live at `/opt/venv`. To use the shell +entrypoint for the same check, replace the image entrypoint explicitly: + +```bash +docker compose run --rm --user "$(id -u):$(id -g)" --entrypoint /bin/bash microduck-software /opt/embodirun/examples/run.sh /workspace/microduck/example.local.yaml check --mode software --json +``` + +The full image adds `--extra simulation` from the same lock and the pinned +EmbodiInfer source. After initializing that submodule and provisioning the +external assets, configure the NVIDIA runtime and prepare a separate local +simulation recipe: + +```bash +git submodule update --init third_party/embodiinfer +docker compose --profile gpu build microduck +export MICRODUCK_ASSETS=/absolute/asset/root +mkdir -p examples/local +docker compose --profile gpu run --rm --user "$(id -u):$(id -g)" microduck init microduck --assets /assets --output /workspace/microduck-simulation +docker compose --profile gpu run --rm --user "$(id -u):$(id -g)" microduck /workspace/microduck-simulation/example.local.yaml check --mode simulation --json +``` + +Set `MICRODUCK_ASSETS` again in a new terminal. Assets mount at `/assets:ro`; +they are not copied into the image. For Docker simulation, use the same Compose +service and container manifest path with `check --mode simulation` or `run`; +the next section's commands use a native environment. To reuse a native manifest, preserve the +original and remove only its top-level `python` from a Docker copy beside it; +an explicit host Python path would override the image's `/opt/venv/bin/python`. +See [installation](../../docs/en/installation.md#containers) for shared +`/tmp` launcher IPC storage and owned shutdown. Default Compose commands show +help. Software checks do not establish CUDA/EGL support, asset compatibility, +model inference or episode success. + +The container software flow was also exercised as a non-root host UID/GID. +Its generated files stayed in `/workspace` with that ownership, both entrypoints +passed plain and JSON checks, and the printed check command worked from another +directory with a manifest path containing spaces. An unavailable environment +returned exit 2 with a repair hint. The task's containers, launcher volume and +network were removed after verification; this was software evidence only. ## Run -With the Host environment installed via `uv sync --frozen`: +Run from the checkout root on the Linux GPU host. The assignments below use +the default environment and manifest; if you chose custom paths, substitute +their original absolute paths and select the scene interpreter as described +above. Set these variables again in a new terminal: ```bash -CONFIG=examples/microduck_vln/example.local.yaml -bash examples/run.sh "$CONFIG" validate -bash examples/run.sh "$CONFIG" plan -bash examples/run.sh "$CONFIG" check -bash examples/run.sh "$CONFIG" run +RECIPE_ENV="$PWD/.venv-microduck" +CONFIG="$PWD/examples/local/microduck/example.local.yaml" +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" validate +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" plan +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" check --mode simulation --json +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" check --mode simulation +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" run ``` -`validate` and `plan` are offline. `check` verifies assets, CUDA, EGL, ONNX, -MPC and the encoder; `run` starts simulation and its inference child. +`validate` and `plan` are offline. `check --mode simulation --json` reports local +environment and path +prerequisites; it does not check CUDA/EGL or model inference. `check` without +`--json` continues into the asset/CUDA/EGL/ONNX/MPC/encoder preflight after the +local checks pass. The full preflight exercises the scene and walking policy +but does not run the learned VLN model. `run` starts simulation and its +inference child; inspect the episode results for task success separately. All settings come from the YAML. Outputs are under -`artifacts/examples/microduck-vln/-run/result/`. +`examples/local/microduck/runs/-run/result/`. Set `parameters.slurm: "off"` on a GPU host, or `auto` to request one GPU outside an existing allocation. `cpus`, `time`, and optional `partition` @@ -110,12 +267,14 @@ configure that request. To keep a run alive after SSH disconnects, set ```bash sbatch --gres=gpu:1 --cpus-per-task=4 --time=01:00:00 \ - --wrap='bash examples/run.sh examples/microduck_vln/example.local.yaml run' + --wrap="\"$RECIPE_ENV/bin/embodirun\" example \"$CONFIG\" run" ``` Add your cluster's partition option if needed. Run `sbatch` from the repository -root. Cancel with `scancel JOB_ID`; termination saves available diagnostics and -stops the inference child. +root with the absolute `RECIPE_ENV` and `CONFIG` defined above. The +wrapped command uses absolute CLI and manifest paths. Cancel with +`scancel JOB_ID`; termination saves available diagnostics and stops the +inference child. ## Episodes and asset integrity @@ -134,12 +293,14 @@ evaluation, select `data/train/eval_val2_40_valid.jsonl` and set `num_episodes: 40`. Episode IDs are metadata, not filenames. When deliberately changing an asset, checkpoint, or inference source, first -check compatibility. Point `parameters.manifest` at a **new** -`assets.local.json`, then provision its inventory: +check compatibility. `init` already created `assets.local.json` from the +reference inventory, so retain it for the reference inputs. For a changed +asset set, point `parameters.manifest` at a **new, absent** +`assets.revised.local.json`, then provision its inventory: ```bash -bash examples/run.sh examples/microduck_vln/example.local.yaml provision -bash examples/run.sh examples/microduck_vln/example.local.yaml check +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" provision +"$RECIPE_ENV/bin/embodirun" example "$CONFIG" check ``` Provisioning does not allocate a GPU and refuses to overwrite an existing @@ -190,17 +351,22 @@ reported behavior; inspect endpoint errors when experimenting with the controlle From the repository root, the normal development environment runs the CPU tests: ```bash -uv run pytest -q tests/test_microduck_vln.py -uv run ruff check src tests examples/microduck_vln integrations/microduck_vln -uv run ruff format --check src tests examples/microduck_vln integrations/microduck_vln +uv run --frozen pytest -q tests/test_microduck_vln.py +uv run --frozen ruff check src tests examples/microduck_vln integrations/microduck_vln +uv run --frozen ruff format --check src tests examples/microduck_vln integrations/microduck_vln bash -n examples/run.sh ``` The HTTP adapter tests skip when Torch/EmbodiInfer are absent. To exercise them -with the dedicated optional environment, without a GPU or checkpoint: +with the dedicated simulation environment, without a GPU or checkpoint, also +prepare pytest as a development tool in that environment; see the integration's +[`test` extra](../../integrations/microduck_vln/README.md). The Recipe's runtime +lock does not include pytest. That extra's installation has not been verified +as a Recipe deployment step; the commands below are developer checks, not an +additional locked installation profile: ```bash -PYTHONPATH="src:third_party/embodiinfer" .venv-microduck/bin/python \ +PYTHONPATH="src:third_party/embodiinfer" "$RECIPE_ENV/bin/python" \ -m pytest -q tests/test_microduck_vln.py tests/test_embodiinfer_http.py ``` diff --git a/examples/microduck_vln/example.yaml b/examples/microduck_vln/example.yaml index 674a2c6..806acf3 100644 --- a/examples/microduck_vln/example.yaml +++ b/examples/microduck_vln/example.yaml @@ -1,7 +1,7 @@ version: 1 name: microduck-vln kind: microduck -python: ../../.venv-microduck/bin/python +# setup prepares .venv-microduck; the launcher selects it automatically. output_dir: ../../artifacts/examples/microduck-vln parameters: project_root: /REPLACE_MICRODUCK_ASSETS diff --git a/examples/microduck_vln/pyproject.toml b/examples/microduck_vln/pyproject.toml new file mode 100644 index 0000000..02330b5 --- /dev/null +++ b/examples/microduck_vln/pyproject.toml @@ -0,0 +1,35 @@ +[project] +name = "embodirun-microduck-vln-recipe" +version = "0.1.0" +description = "Locked offline configuration and optional MicroDuck VLN simulation environment" +requires-python = ">=3.12,<3.13" +dependencies = [ + "embodirun", + "embodirun-microduck", + "paramiko>=3.4,<5", + "PyYAML>=6,<7", + "rich>=13,<15", +] + +[project.optional-dependencies] +simulation = [ + "embodirun-microduck[full]", + "torch==2.10.0", + "torchvision==0.25.0", +] + +[tool.uv] +package = false +required-version = ">=0.12.0,<0.13" +default-groups = [] + +[tool.uv.sources] +embodirun = { path = "../..", editable = true } +embodirun-microduck = { path = "../../integrations/microduck_vln", editable = true } +torch = [{ index = "pytorch-cu130", marker = "sys_platform == 'linux'" }] +torchvision = [{ index = "pytorch-cu130", marker = "sys_platform == 'linux'" }] + +[[tool.uv.index]] +name = "pytorch-cu130" +url = "https://download.pytorch.org/whl/cu130" +explicit = true diff --git a/examples/microduck_vln/uv.lock b/examples/microduck_vln/uv.lock new file mode 100644 index 0000000..a0c868c --- /dev/null +++ b/examples/microduck_vln/uv.lock @@ -0,0 +1,1240 @@ +version = 1 +revision = 3 +requires-python = "==3.12.*" +resolution-markers = [ + "sys_platform == 'linux'", + "sys_platform != 'linux'", +] + +[[package]] +name = "absl-py" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/4f/d79676ab82f2e42fc3611618139f13a9c4c31d0cff4b486982047679a802/absl_py-2.5.0.tar.gz", hash = "sha256:0c996f25c0490700fadabe6351630f6111534fa0ae252cc6d2014ea3b141135f", size = 118119, upload-time = "2026-07-03T10:57:48.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/0a/a10b45aab35b175aded078a462dc8d0c698f5b13946e7cb0869097b78bb6/absl_py-2.5.0-py3-none-any.whl", hash = "sha256:0f17b89f2a4eaaedc4f28c622998aa690564b3012a396a4ffad0821007fe03ba", size = 137410, upload-time = "2026-07-03T10:57:46.735Z" }, +] + +[[package]] +name = "accelerate" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.10.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torch", version = "2.10.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" }, marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/b5/1d3ed029ac71d3f2961346829a268da923698e9fd63f218f78841f216bfd/accelerate-1.15.0.tar.gz", hash = "sha256:5654f8c5eaa0d4fa68b33e287a97765da6849bf6d51dcac874e73fbbddfb6134", size = 422615, upload-time = "2026-09-09T13:04:49.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/4c/34f0450479d01195027260da68d8a3880683f1640c3ca5adf64acb3185f1/accelerate-1.15.0-py3-none-any.whl", hash = "sha256:97eacca0b73e45cb867dbf8c5d5d4dc32219544300e0c8992c7334dc2ef33cec", size = 394295, upload-time = "2026-09-09T13:04:47.331Z" }, +] + +[[package]] +name = "bcrypt" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, + { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, + { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, + { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, + { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, + { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, +] + +[[package]] +name = "casadi" +version = "3.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/62/1e98662024915ecb09c6894c26a3f497f4afa66570af3f53db4651fc45f1/casadi-3.7.2.tar.gz", hash = "sha256:b4d7bd8acdc4180306903ae1c9eddaf41be2a3ae2fa7154c57174ae64acdc60d", size = 6053600, upload-time = "2025-09-10T10:05:49.521Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/c8/689d085447b1966f42bdb8aa4fbebef49a09697dbee32ab02a865c17ac1b/casadi-3.7.2-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:309ea41a69c9230390d349b0dd899c6a19504d1904c0756bef463e47fb5c8f9a", size = 47116756, upload-time = "2025-09-10T07:53:00.931Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/3c4704394a6fd4dfb2123a4fd71ba64a001f340670a3eba45be7a19ac736/casadi-3.7.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6033381234db810b2247d16c6352e679a009ec4365d04008fc768866e011ed58", size = 42293718, upload-time = "2025-09-10T08:07:16.415Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/4cf05469ddf8544da5e92f359f96d716a97e7482999f085a632bc4ef344a/casadi-3.7.2-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:732f2804d0766454bb75596339e4f2da6662ffb669621da0f630ed4af9e83d6a", size = 47276175, upload-time = "2025-09-10T08:08:09.29Z" }, + { url = "https://files.pythonhosted.org/packages/82/08/b5f57fea03128efd5c860673b6ac44776352e6c1af862b8177f4c503fffe/casadi-3.7.2-cp312-none-manylinux2014_i686.whl", hash = "sha256:cf17298ff0c162735bdf9bf72b765c636ae732130604017a3b52e26e35402857", size = 72430454, upload-time = "2025-09-10T08:09:10.781Z" }, + { url = "https://files.pythonhosted.org/packages/24/ab/d7233c915b12c005655437c6c4cf0ae46cbbb2b20d743cb5e4881ad3104a/casadi-3.7.2-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:cde616930fa1440ad66f1850670399423edd37354eed9b12e74b3817b98d1187", size = 75568903, upload-time = "2025-09-10T08:10:07.108Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/5b984124f539656efdf079f3d8f09d73667808ec8d0546e6bce6dc60ade6/casadi-3.7.2-cp312-none-win_amd64.whl", hash = "sha256:81d677d2b020c1307c1eb25eae15686e5de199bb066828c3eaabdfaaaf457ffd", size = 50991347, upload-time = "2025-09-10T08:10:46.629Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "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/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" }, +] + +[[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 = "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/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 = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "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/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" }, +] + +[[package]] +name = "cuda-bindings" +version = "13.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/3c/c33fd3aa5fcc89aa1c135e477a0561f29142ab5fe028ca425fc87f7f0a74/cuda_bindings-13.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b899e5a513c11eaa18648f9bf5265d8de2a93f76ef66a6bfca0a2887303965cd", size = 11709086, upload-time = "2025-10-21T15:09:00.005Z" }, + { url = "https://files.pythonhosted.org/packages/21/ac/6b34452a3836c9fbabcd360689a353409d15f500dd9d9ced7f837549e383/cuda_bindings-13.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf41d9e69019939aa15296fa66ea7d3fdb8d2c6383f729f4b1156c8b37808a06", size = 12128303, upload-time = "2025-10-21T15:09:02.889Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fb/f8e1890428f9f590b4beebd63b068aac1ce32a3331510c847b9f9a78f261/cuda_pathfinder-1.8.3-py3-none-any.whl", hash = "sha256:e29e59829c297a7a5233bd9cc71094fc5bddbd076951482670178f9eade39b1f", size = 62561, upload-time = "2026-10-02T03:20:23.712Z" }, +] + +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, +] + +[[package]] +name = "embodirun" +version = "0.1.0" +source = { editable = "../../" } + +[package.metadata] +requires-dist = [ + { name = "sglang", extras = ["diffusion"], marker = "python_full_version >= '3.12' and sys_platform == 'linux' and extra == 'sglang'", specifier = "==0.5.18" }, + { name = "wireless-comm", marker = "extra == 'wireless'", git = "https://github.com/BUAA-CI-LAB/WirelessComm?rev=v0.1.0" }, +] +provides-extras = ["sglang", "wireless"] + +[package.metadata.requires-dev] +dev = [ + { name = "numpy", specifier = ">=1.26,<3" }, + { name = "paramiko", specifier = ">=3.4,<5" }, + { name = "pillow", specifier = ">=10,<13" }, + { name = "pytest", specifier = ">=8,<9" }, + { name = "pyyaml", specifier = ">=6,<7" }, + { name = "rich", specifier = ">=13,<15" }, + { name = "ruff", specifier = ">=0.10" }, +] +host = [ + { name = "paramiko", specifier = ">=3.4,<5" }, + { name = "pyyaml", specifier = ">=6,<7" }, + { name = "rich", specifier = ">=13,<15" }, +] +robot-arx5 = [ + { name = "opencv-python-headless", specifier = ">=4.10,<5" }, + { name = "pillow", specifier = ">=10,<13" }, + { name = "pyrealsense2", specifier = ">=2.54,<3" }, +] +robot-fr3 = [{ name = "franky-control", specifier = ">=1.1.4,<2" }] +robot-go2 = [] +robot-so101 = [ + { name = "feetech-servo-sdk", marker = "python_full_version >= '3.12'", specifier = ">=1,<2" }, + { name = "opencv-python-headless", marker = "python_full_version >= '3.12'", specifier = ">=4.10,<5" }, +] +robot-xlerobot-external-owner = [] +sim-habitat = [ + { name = "habitat-sim", marker = "python_full_version == '3.11.*'", git = "https://github.com/facebookresearch/habitat-sim.git?rev=acbe6f4922e68145e401e55c30f9dfea460a3f24" }, + { name = "opencv-python-headless", marker = "python_full_version == '3.11.*'", specifier = ">=4.10,<5" }, + { name = "pillow", marker = "python_full_version == '3.11.*'", specifier = ">=10,<13" }, +] +sim-isaac = [ + { name = "isaacsim", extras = ["all", "extscache"], marker = "python_full_version == '3.12.*'", specifier = "==6.0.1.0" }, + { name = "opencv-python-headless", marker = "python_full_version == '3.12.*'", specifier = ">=4.10,<5" }, + { name = "pillow", marker = "python_full_version == '3.12.*'", specifier = ">=10,<13" }, +] +sim-libero = [ + { name = "lerobot", extras = ["libero"], marker = "python_full_version >= '3.12'", specifier = ">=0.6,<0.7" }, + { name = "pillow", marker = "python_full_version >= '3.12'", specifier = ">=10,<13" }, +] +sim-vlabench = [ + { name = "lerobot", marker = "python_full_version >= '3.12'", specifier = ">=0.6,<0.7" }, + { name = "pillow", marker = "python_full_version >= '3.12'", specifier = ">=10,<13" }, + { name = "vlabench", marker = "python_full_version >= '3.12'", git = "https://github.com/OpenMOSS/VLABench.git?rev=cf588fe60c0c7282174fe979f5913170cfe69017" }, +] + +[[package]] +name = "embodirun-microduck" +version = "0.1.0" +source = { editable = "../../integrations/microduck_vln" } +dependencies = [ + { name = "embodirun" }, +] + +[package.optional-dependencies] +full = [ + { name = "accelerate" }, + { name = "casadi" }, + { name = "einops" }, + { name = "huggingface-hub" }, + { name = "imageio-ffmpeg" }, + { name = "mujoco" }, + { name = "numpy" }, + { name = "onnxruntime" }, + { name = "pillow" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "torch", version = "2.10.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torch", version = "2.10.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" }, marker = "sys_platform == 'linux'" }, + { name = "torchvision", version = "0.25.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torchvision", version = "0.25.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" }, marker = "sys_platform == 'linux'" }, + { name = "transformers" }, +] + +[package.metadata] +requires-dist = [ + { name = "accelerate", marker = "extra == 'full'", specifier = ">=1,<2" }, + { name = "accelerate", marker = "extra == 'service'", specifier = ">=1,<2" }, + { name = "casadi", marker = "extra == 'full'", specifier = "==3.7.2" }, + { name = "casadi", marker = "extra == 'simulation'", specifier = "==3.7.2" }, + { name = "einops", marker = "extra == 'full'", specifier = ">=0.8,<1" }, + { name = "einops", marker = "extra == 'service'", specifier = ">=0.8,<1" }, + { name = "embodirun", specifier = ">=0.1.0" }, + { name = "huggingface-hub", marker = "extra == 'full'", specifier = "==0.36.0" }, + { name = "huggingface-hub", marker = "extra == 'service'", specifier = "==0.36.0" }, + { name = "imageio-ffmpeg", marker = "extra == 'full'", specifier = ">=0.6,<0.7" }, + { name = "imageio-ffmpeg", marker = "extra == 'simulation'", specifier = ">=0.6,<0.7" }, + { name = "mujoco", marker = "extra == 'full'", specifier = "==3.8.1" }, + { name = "mujoco", marker = "extra == 'simulation'", specifier = "==3.8.1" }, + { name = "numpy", marker = "extra == 'full'", specifier = ">=1.26,<2" }, + { name = "numpy", marker = "extra == 'service'", specifier = ">=1.26,<2" }, + { name = "numpy", marker = "extra == 'simulation'", specifier = ">=1.26,<2" }, + { name = "numpy", marker = "extra == 'test'", specifier = ">=1.26,<2" }, + { name = "onnxruntime", marker = "extra == 'full'", specifier = ">=1.20,<2" }, + { name = "onnxruntime", marker = "extra == 'simulation'", specifier = ">=1.20,<2" }, + { name = "pillow", marker = "extra == 'full'", specifier = ">=10,<13" }, + { name = "pillow", marker = "extra == 'service'", specifier = ">=10,<13" }, + { name = "pillow", marker = "extra == 'simulation'", specifier = ">=10,<13" }, + { name = "pillow", marker = "extra == 'test'", specifier = ">=10,<13" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=8,<9" }, + { name = "safetensors", marker = "extra == 'full'", specifier = ">=0.4,<1" }, + { name = "safetensors", marker = "extra == 'service'", specifier = ">=0.4,<1" }, + { name = "tokenizers", marker = "extra == 'full'", specifier = "==0.21.4" }, + { name = "tokenizers", marker = "extra == 'service'", specifier = "==0.21.4" }, + { name = "torch", marker = "extra == 'full'", specifier = ">=2.5,<2.12" }, + { name = "torch", marker = "extra == 'service'", specifier = ">=2.5,<2.12" }, + { name = "torchvision", marker = "extra == 'full'", specifier = ">=0.20,<0.27" }, + { name = "torchvision", marker = "extra == 'service'", specifier = ">=0.20,<0.27" }, + { name = "transformers", marker = "extra == 'full'", specifier = "==4.51.3" }, + { name = "transformers", marker = "extra == 'service'", specifier = "==4.51.3" }, +] +provides-extras = ["simulation", "service", "test", "full"] + +[[package]] +name = "embodirun-microduck-vln-recipe" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "embodirun" }, + { name = "embodirun-microduck" }, + { name = "paramiko" }, + { name = "pyyaml" }, + { name = "rich" }, +] + +[package.optional-dependencies] +simulation = [ + { name = "embodirun-microduck", extra = ["full"] }, + { name = "torch", version = "2.10.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torch", version = "2.10.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" }, marker = "sys_platform == 'linux'" }, + { name = "torchvision", version = "0.25.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torchvision", version = "0.25.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" }, marker = "sys_platform == 'linux'" }, +] + +[package.metadata] +requires-dist = [ + { name = "embodirun", editable = "../../" }, + { name = "embodirun-microduck", editable = "../../integrations/microduck_vln" }, + { name = "embodirun-microduck", extras = ["full"], marker = "extra == 'simulation'", editable = "../../integrations/microduck_vln" }, + { name = "paramiko", specifier = ">=3.4,<5" }, + { name = "pyyaml", specifier = ">=6,<7" }, + { name = "rich", specifier = ">=13,<15" }, + { name = "torch", marker = "sys_platform == 'linux' and extra == 'simulation'", specifier = "==2.10.0", index = "https://download.pytorch.org/whl/cu130" }, + { name = "torch", marker = "sys_platform != 'linux' and extra == 'simulation'", specifier = "==2.10.0" }, + { name = "torchvision", marker = "sys_platform == 'linux' and extra == 'simulation'", specifier = "==0.25.0", index = "https://download.pytorch.org/whl/cu130" }, + { name = "torchvision", marker = "sys_platform != 'linux' and extra == 'simulation'", specifier = "==0.25.0" }, +] +provides-extras = ["simulation"] + +[[package]] +name = "etils" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/ce/6e067242fde898841922ac6fc82b0bb2fe35c38e995880bdffdfbe30182a/etils-1.14.0.tar.gz", hash = "sha256:8136e7f4c4173cd0af0ca5481c4475152f0b8686192951eefa60ee8711e1ede4", size = 108127, upload-time = "2026-03-04T17:41:36.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/3d/589663aeeacd59bb2f3e8596bfd3e81cf0fb18d70bb433199041f469771b/etils-1.14.0-py3-none-any.whl", hash = "sha256:b5df7341f54dbe1405a4450b2741207b4a8c279780402b45f87202b94dfc52b4", size = 172934, upload-time = "2026-03-04T17:41:35.01Z" }, +] + +[package.optional-dependencies] +epath = [ + { name = "fsspec" }, + { name = "typing-extensions" }, + { name = "zipp" }, +] + +[[package]] +name = "filelock" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/e4/34efcb869715cf299e47d1ac7b2624d2bcb6f2d3dffc2f0abe8417f65ab2/filelock-4.0.12.tar.gz", hash = "sha256:cf42711a7ac791818b299fab0332a088c65aeeefa36290de98db92c434303b0c", size = 253880, upload-time = "2026-10-05T13:17:43.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/a1/b047e1c486a2c66283633b7dbb73872ca16ca1ce6a426f70d4d037fc8e1c/filelock-4.0.12-py3-none-any.whl", hash = "sha256:5f17ee83ecee8a6f3e389c75822fb70a1c2f0506b99438a6dffa1d56793588c8", size = 111732, upload-time = "2026-10-05T13:17:42.361Z" }, +] + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, +] + +[[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 = "glfw" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/62/096058bcb4b4fb28f7ecd28fb048f07969d90b243c417af5f6d09d45a0c2/glfw-2.10.2.tar.gz", hash = "sha256:5d2cf97c66bc42a6b583be0e307eae5a3945438322e2ed0c5e4f14dc251d693d", size = 36307, upload-time = "2026-07-21T14:42:36.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/80/5e575ec14f54dc0a644e7215985b9ee7c5044fcc3594d6b83dfdeb04ccd4/glfw-2.10.2-py2.py3-none-macosx_10_6_intel.whl", hash = "sha256:a04d25bb535a3a29e173ea1abd658e161681b5d3816e00bc51c122f74cd7fa3a", size = 110295, upload-time = "2026-07-21T14:42:25.625Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2e/22216db429690aa51fa87d2e2b5e6b610c5a37b9df7768da2927f52f8704/glfw-2.10.2-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:c000b8a5e5fb4374b2c18d12347255ccb92001b9bcf80ef74c56072acbb98fe3", size = 107146, upload-time = "2026-07-21T14:42:26.854Z" }, + { url = "https://files.pythonhosted.org/packages/75/a4/9ccee9d5c3b9c5d6bafce3e8d53ab8318eff30124e3c19b95580bebaa6d4/glfw-2.10.2-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:0ffe2b48b51f37b05e8f027f57c0b8cd213affaf22fc83401bd9621692d4c6c8", size = 235005, upload-time = "2026-07-21T14:42:27.955Z" }, + { url = "https://files.pythonhosted.org/packages/77/44/37aeed50c581c76e1c5bb83b696a06f98f8ecc979ed8564dde3eef908e8b/glfw-2.10.2-py2.py3-none-manylinux2014_x86_64.whl", hash = "sha256:82daf1f87b2a48815637dc6f6720d4a55a57ba1d3683322dc20dc28065754f97", size = 246951, upload-time = "2026-07-21T14:42:29.107Z" }, + { url = "https://files.pythonhosted.org/packages/52/fc/29b4f1a8c8e33d8934781330a0845458b01da7f389d9fe6a3350e752b7f3/glfw-2.10.2-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:7604425ec95665cc6a25c3ed7d07e9660d394a34472ceffaec0843c844e7b649", size = 236018, upload-time = "2026-07-21T14:42:30.623Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/1b98671314ea5f40397586b6cd14db913de3196333be558fdc177e61708f/glfw-2.10.2-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:b860de3ca0686182483f98f3ddd12e660acf25b3e0d521450ec9a3f999f72a65", size = 248490, upload-time = "2026-07-21T14:42:32.377Z" }, + { url = "https://files.pythonhosted.org/packages/12/d9/bd2e6c8dbadcf69fcd2289705e75881258b46221c196903a52e9f6e97322/glfw-2.10.2-py2.py3-none-win32.whl", hash = "sha256:a94aefe8c48886fd83cd6e11e936846166a4b5b94abf1f4bf599d984774b0b1b", size = 557654, upload-time = "2026-07-21T14:42:33.903Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6c/4ca5f3ab85a8d7f612ce857208726e9b834e54e559751e3d4f98cc2f7509/glfw-2.10.2-py2.py3-none-win_amd64.whl", hash = "sha256:15fd0666cd8f1b0ecb535c3abb712b8ddda053bf8d16de2353b1c6ced0e65402", size = 564433, upload-time = "2026-07-21T14:42:35.465Z" }, +] + +[[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/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 = "huggingface-hub" +version = "0.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/63/4910c5fa9128fdadf6a9c5ac138e8b1b6cee4ca44bf7915bbfbce4e355ee/huggingface_hub-0.36.0.tar.gz", hash = "sha256:47b3f0e2539c39bf5cde015d63b72ec49baff67b6931c3d97f3f84532e2b8d25", size = 463358, upload-time = "2025-10-23T12:12:01.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl", hash = "sha256:7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d", size = 566094, upload-time = "2025-10-23T12:11:59.557Z" }, +] + +[[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 = "imageio-ffmpeg" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/bd/c3343c721f2a1b0c9fc71c1aebf1966a3b7f08c2eea8ed5437a2865611d6/imageio_ffmpeg-0.6.0.tar.gz", hash = "sha256:e2556bed8e005564a9f925bb7afa4002d82770d6b08825078b7697ab88ba1755", size = 25210, upload-time = "2025-01-16T21:34:32.747Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl", hash = "sha256:9d2baaf867088508d4a3458e61eeb30e945c4ad8016025545f66c4b5aaef0a61", size = 24932969, upload-time = "2025-01-16T21:34:20.464Z" }, + { url = "https://files.pythonhosted.org/packages/40/5c/f3d8a657d362cc93b81aab8feda487317da5b5d31c0e1fdfd5e986e55d17/imageio_ffmpeg-0.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b1ae3173414b5fc5f538a726c4e48ea97edc0d2cdc11f103afee655c463fa742", size = 21113891, upload-time = "2025-01-16T21:34:00.277Z" }, + { url = "https://files.pythonhosted.org/packages/33/e7/1925bfbc563c39c1d2e82501d8372734a5c725e53ac3b31b4c2d081e895b/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1d47bebd83d2c5fc770720d211855f208af8a596c82d17730aa51e815cdee6dc", size = 25632706, upload-time = "2025-01-16T21:33:53.475Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2d/43c8522a2038e9d0e7dbdf3a61195ecc31ca576fb1527a528c877e87d973/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c7e46fcec401dd990405049d2e2f475e2b397779df2519b544b8aab515195282", size = 29498237, upload-time = "2025-01-16T21:34:13.726Z" }, + { url = "https://files.pythonhosted.org/packages/a0/13/59da54728351883c3c1d9fca1710ab8eee82c7beba585df8f25ca925f08f/imageio_ffmpeg-0.6.0-py3-none-win32.whl", hash = "sha256:196faa79366b4a82f95c0f4053191d2013f4714a715780f0ad2a68ff37483cc2", size = 19652251, upload-time = "2025-01-16T21:34:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl", hash = "sha256:02fa47c83703c37df6bfe4896aab339013f62bf02c5ebf2dce6da56af04ffc0a", size = 31246824, upload-time = "2025-01-16T21:34:28.6Z" }, +] + +[[package]] +name = "invoke" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/227c48c5fe47fa178ccf1fda8f047d16c97ba926567b661e9ce2045c600c/invoke-3.0.3.tar.gz", hash = "sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c", size = 343419, upload-time = "2026-04-07T15:17:48.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/de/bbc12563bbf979618d17625a4e753ff7a078523e28d870d3626daa97261a/invoke-3.0.3-py3-none-any.whl", hash = "sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053", size = 160958, upload-time = "2026-04-07T15:17:46.875Z" }, +] + +[[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 = "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/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" }, +] + +[[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 = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "mujoco" +version = "3.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "etils", extra = ["epath"] }, + { name = "glfw" }, + { name = "numpy" }, + { name = "pyopengl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/ba/ad135f7a4a71360072bc4f202f7ab3130d7d7827cff70ce3e1b382a1a410/mujoco-3.8.1.tar.gz", hash = "sha256:019a0b3406892bc98454eaf55cbd27f85d167758ad785f77c608a61f3a34ad17", size = 922269, upload-time = "2026-05-11T13:44:01.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/4f/ccfaf99fab0042bef61da2cd21d47f41fcff069744b42bc6829d9da6b4fc/mujoco-3.8.1-cp312-cp312-macosx_10_16_x86_64.whl", hash = "sha256:b1c5213eb8fc53ee8c0713391eda5f0766088766025e8b46e1dfd3cb9f00db71", size = 7356297, upload-time = "2026-05-11T13:43:30.654Z" }, + { url = "https://files.pythonhosted.org/packages/ec/33/64e4de2ff6f0ead6c9d204efdd68aea3ca235058829f69b957625768495d/mujoco-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06e36128883f484e173d02e6140ae09c1ff9845dbc7fb605b02361d9d2ecaccf", size = 7245562, upload-time = "2026-05-11T13:43:32.736Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/597263af3519b1ac1c09bb8fc8f8985e90e4990c8aff62ca1a0c035e824c/mujoco-3.8.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e4e951de3ab2ba5187b4807c648ac551fd6ae27ff04c7e25304b357d35d7fe8", size = 6784316, upload-time = "2026-05-11T13:43:34.837Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2f/8a911be3ed84436bc46b5bdcf4ead6ac9590d0ddc82c006834c49ffd60c4/mujoco-3.8.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2970c90913a52b31eade56f27e5439905f08f1479e7991e257144ca829fa1267", size = 7322546, upload-time = "2026-05-11T13:43:37.074Z" }, + { url = "https://files.pythonhosted.org/packages/83/58/d580c4abf7dda5231fed0886a626e1815e40c2d118bf7d0052a617435d6e/mujoco-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:496b61c863d544e076990d1601555f71e522eb3b7aaae227dfbc5dea522f88a1", size = 5893327, upload-time = "2026-05-11T13:43:39.022Z" }, +] + +[[package]] +name = "networkx" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/76/3af777226b63a5e64a6b36b1ec5855c14e2b94a37096d4760e595fc43511/networkx-3.7.tar.gz", hash = "sha256:fd77a511bd90f39f3d016351345b52cf5319b813bdca01de3f755d3cca62e96a", size = 1866482, upload-time = "2026-09-21T16:45:16.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/cd/fe58041e9011f307c490e3e17dd48cc516448f7c698a3f2d9d9d65d7e6a8/networkx-3.7-py3-none-any.whl", hash = "sha256:e3fd2c13a7814cee3746340d8d7f8598a67f16a58bf47fb7f8793fab6efca1b0", size = 2142205, upload-time = "2026-09-21T16:45:14.609Z" }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.0.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.15.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/93/b3c9db2c35d6183361333d2dcfea50e094c012d012c8a4d7effbfb53ef62/nvidia_cudnn_cu13-9.15.1.9-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:44cd2ec83c3ef62a7357614bd02ce7f3dac35ffcbb04ad20999e730741f0ba17", size = 415636241, upload-time = "2025-11-12T20:22:26.582Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d0/90f98fc55c48a7d8f5ad0a03a6321acc1a7024bdd550d96b3547a04ea6b4/nvidia_cudnn_cu13-9.15.1.9-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ebc9a647918df0d7298d67cfaf41579fd4c78ead9aba246f5ad9414d61b9ec4c", size = 351298418, upload-time = "2025-11-12T20:23:21.455Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.28.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, +] + +[[package]] +name = "onnxruntime" +version = "1.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/6f/48169f2e62b405bff5053cbd1d73fb5ce41ef7ecd13bb3bfcc191e689b8a/onnxruntime-1.30.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:001ed726c9bd5e2bc92faade7d37d889e9606a350b7d5529f0227df2e3bb57fd", size = 21544867, upload-time = "2026-09-10T16:31:19.876Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/cbc5b8f91963689fdd622f463508c01d0aa95d3f944747b1e0b1eb2160b8/onnxruntime-1.30.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6c32a000d5139a38ba9349030b0032e3331acb559d596b22738d9d2b343a2b83", size = 21345202, upload-time = "2026-09-10T16:31:23.361Z" }, + { url = "https://files.pythonhosted.org/packages/34/35/e7f862dbacbc99fadd9b14a614e49c99bf0f35fd9927a82f096e3de33531/onnxruntime-1.30.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:fa688e7891a6aa206636fe7372e27ee75fd17713289f6b4fc7b190e0a7de9328", size = 23585654, upload-time = "2026-09-10T16:31:26.65Z" }, + { url = "https://files.pythonhosted.org/packages/a6/13/0f1699f6de549c9324bc9112a2a85b14c517904cd11b562a654643b755a1/onnxruntime-1.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3501472571f1b1eee50e017851e7929f5ea37312d2d8c2494a19e8fc58b4a38", size = 14311470, upload-time = "2026-09-10T16:31:31.273Z" }, + { url = "https://files.pythonhosted.org/packages/a2/17/02b13e5f51461f0453b18ab854e2d0bc1b6ec353241b05a2ee6b79e35d87/onnxruntime-1.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:dc4c706f1935ebb62356e6a095b047859badd854482c40560888e95c328ed262", size = 14175072, upload-time = "2026-09-10T16:31:34.541Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "paramiko" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bcrypt" }, + { name = "cryptography" }, + { name = "invoke" }, + { name = "pynacl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/e7/81fdcbc7f190cdb058cffc9431587eb289833bdd633e2002455ca9bb13d4/paramiko-4.0.0.tar.gz", hash = "sha256:6a25f07b380cc9c9a88d2b920ad37167ac4667f8d9886ccebd8f90f654b5d69f", size = 1630743, upload-time = "2025-08-04T01:02:03.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl", hash = "sha256:0e20e00ac666503bf0b4eda3b6d833465a2b7aff2e2b3d79a8bba5ef144ee3b9", size = 223932, upload-time = "2025-08-04T01:02:02.029Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, +] + +[[package]] +name = "protobuf" +version = "7.36.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/89/5b8517baa72f84a67b8a307ba953c91057af618bf40bf676f3c03551f8f0/protobuf-7.36.2.tar.gz", hash = "sha256:497d0463ff3316681da6c0b9e8d06cb465d61abce00b613ab42226175644d1bb", size = 512737, upload-time = "2026-09-17T20:07:59.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/72/98342feb672507c8f3a69e34b4fa8961f608edba5c1a48a6f47156d92cb5/protobuf-7.36.2-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:cbc70b17ee27e28894c7fee8bb04be1abead49e936bc70eb60052531eee2079e", size = 456039, upload-time = "2026-09-17T20:07:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ea/91fdf7c2b8bbd49cde056f00a9df6773532987e1c00fe2830b895af95c7e/protobuf-7.36.2-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e", size = 344219, upload-time = "2026-09-17T20:07:52.914Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/5fd5f8ece73fad885c5a09aa849b32d70472f954ba3a92d3bb5974ea953b/protobuf-7.36.2-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:f4fee11ec330d238b34a05c9b675f693c20415d1c5bd7d5320cc2f8a798eb9cf", size = 357223, upload-time = "2026-09-17T20:07:53.985Z" }, + { url = "https://files.pythonhosted.org/packages/db/f3/3996583dd2906297a637af12114deddf7658af6e683fedb83be061983fb5/protobuf-7.36.2-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2", size = 343223, upload-time = "2026-09-17T20:07:54.931Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1b/dcc64f358fcb51811b58ae40b3d28f820725f116d86487cc20bd4b130701/protobuf-7.36.2-cp310-abi3-win32.whl", hash = "sha256:912c1221170e16c08d1f086762f563dd61ff83c18b5fa6652952dfaded66f728", size = 442998, upload-time = "2026-09-17T20:07:55.826Z" }, + { url = "https://files.pythonhosted.org/packages/8a/55/b77bda4e5e5f5971fb51b07663694690e9afdb9402136c16a522bd621cad/protobuf-7.36.2-cp310-abi3-win_amd64.whl", hash = "sha256:a300819d441e078a5608c0d3c709796bb548136058fda017ae51d425b44fd353", size = 456514, upload-time = "2026-09-17T20:07:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/d52c7016b04b6c5108f26691f9d33ec82a9b65d041f1a9c771137693d618/protobuf-7.36.2-py3-none-any.whl", hash = "sha256:bdb3a345d48db958e6ce1f18e508beb0cc981d64f24088427549c866cd039f1e", size = 179806, upload-time = "2026-09-17T20:07:58.211Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +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 = "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 = "pynacl" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, + { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, +] + +[[package]] +name = "pyopengl" +version = "3.1.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/16/912b7225d56284859cd9a672827f18be43f8012f8b7b932bc4bd959a298e/pyopengl-3.1.10.tar.gz", hash = "sha256:c4a02d6866b54eb119c8e9b3fb04fa835a95ab802dd96607ab4cdb0012df8335", size = 1915580, upload-time = "2025-08-18T02:33:01.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/e4/1ba6f44e491c4eece978685230dde56b14d51a0365bc1b774ddaa94d14cd/pyopengl-3.1.10-py3-none-any.whl", hash = "sha256:794a943daced39300879e4e47bd94525280685f42dbb5a998d336cfff151d74f", size = 3194996, upload-time = "2025-08-18T02:32:59.902Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, +] + +[[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/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" }, +] + +[[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 = "14.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "setuptools" +version = "84.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.21.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253, upload-time = "2025-07-28T15:48:54.325Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987, upload-time = "2025-07-28T15:48:44.877Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457, upload-time = "2025-07-28T15:48:43.265Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624, upload-time = "2025-07-28T13:22:43.895Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681, upload-time = "2025-07-28T13:22:47.499Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445, upload-time = "2025-07-28T15:48:39.711Z" }, + { url = "https://files.pythonhosted.org/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014, upload-time = "2025-07-28T13:22:49.569Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197, upload-time = "2025-07-28T13:22:51.471Z" }, + { url = "https://files.pythonhosted.org/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426, upload-time = "2025-07-28T15:48:41.439Z" }, + { url = "https://files.pythonhosted.org/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127, upload-time = "2025-07-28T15:48:46.472Z" }, + { url = "https://files.pythonhosted.org/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243, upload-time = "2025-07-28T15:48:48.539Z" }, + { url = "https://files.pythonhosted.org/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237, upload-time = "2025-07-28T15:48:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980, upload-time = "2025-07-28T15:48:52.325Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871, upload-time = "2025-07-28T15:48:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" }, +] + +[[package]] +name = "torch" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "sys_platform != 'linux'", +] +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, + { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5c/dee910b87c4d5c0fcb41b50839ae04df87c1cfc663cf1b5fca7ea565eeaa/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6d3707a61863d1c4d6ebba7be4ca320f42b869ee657e9b2c21c736bf17000294", size = 79498198, upload-time = "2026-01-21T16:24:34.704Z" }, +] + +[[package]] +name = "torch" +version = "2.10.0+cu130" +source = { registry = "https://download.pytorch.org/whl/cu130" } +resolution-markers = [ + "sys_platform == 'linux'", +] +dependencies = [ + { name = "cuda-bindings" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas" }, + { name = "nvidia-cuda-cupti" }, + { name = "nvidia-cuda-nvrtc" }, + { name = "nvidia-cuda-runtime" }, + { name = "nvidia-cudnn-cu13" }, + { name = "nvidia-cufft" }, + { name = "nvidia-cufile" }, + { name = "nvidia-curand" }, + { name = "nvidia-cusolver" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-cusparselt-cu13" }, + { name = "nvidia-nccl-cu13" }, + { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvshmem-cu13" }, + { name = "nvidia-nvtx" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.10.0%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4fc8f67637f4c92b989a07d80ffe755e79a3510ca02ebf23ce66396fb277c88d", upload-time = "2026-01-21T19:01:36Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.10.0%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:858f0cbcc78d726fea9499eb3464faa98392fa093845a3262209bd226b7844d6", upload-time = "2026-01-21T19:02:22Z" }, +] + +[[package]] +name = "torchvision" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "sys_platform != 'linux'", +] +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch", version = "2.10.0", source = { registry = "https://pypi.org/simple" } }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/3a/6ea0d73f49a9bef38a1b3a92e8dd455cea58470985d25635beab93841748/torchvision-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2abe430c90b1d5e552680037d68da4eb80a5852ebb1c811b2b89d299b10573b", size = 1874920, upload-time = "2026-01-21T16:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/ad/16/8f650c2e288977cf0f8f85184b90ee56ed170a4919347fc74ee99286ed6f/torchvision-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9c55ae8d673ab493325d1267cbd285bb94d56f99626c00ac4644de32a59ede3", size = 4303059, upload-time = "2026-01-21T16:27:11.08Z" }, +] + +[[package]] +name = "torchvision" +version = "0.25.0+cu130" +source = { registry = "https://download.pytorch.org/whl/cu130" } +resolution-markers = [ + "sys_platform == 'linux'", +] +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch", version = "2.10.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" } }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.25.0%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3646e6c8fa5066da392d0ff13002cc683301386fe1933f8f1432fc5292e5d288", upload-time = "2026-01-21T22:34:43Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.25.0%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c2f5e38f0cd57a2796e4503c0f13365deba01dbc167ef820f0beec7ca96f5f2e", upload-time = "2026-01-21T22:34:44Z" }, +] + +[[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 = "transformers" +version = "4.51.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/11/7414d5bc07690002ce4d7553602107bf969af85144bbd02830f9fb471236/transformers-4.51.3.tar.gz", hash = "sha256:e292fcab3990c6defe6328f0f7d2004283ca81a7a07b2de9a46d67fd81ea1409", size = 8941266, upload-time = "2025-04-14T08:15:00.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/b6/5257d04ae327b44db31f15cce39e6020cc986333c715660b1315a9724d82/transformers-4.51.3-py3-none-any.whl", hash = "sha256:fd3279633ceb2b777013234bbf0b4f5c2d23c4626b05497691f00cfda55e8a83", size = 10383940, upload-time = "2025-04-14T08:13:43.023Z" }, +] + +[[package]] +name = "triton" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "urllib3" +version = "2.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 = "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 = "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" }, +] diff --git a/examples/runner.py b/examples/runner.py index 3e09683..e7979d4 100644 --- a/examples/runner.py +++ b/examples/runner.py @@ -7,12 +7,14 @@ from __future__ import annotations import argparse +import contextlib import hashlib import json import math import os import re import shlex +import shutil import signal import subprocess import sys @@ -26,6 +28,9 @@ from embodirun.deployment.config import load_config from embodirun.deployment.config.loader import _load_yaml from embodirun.deployment.plan import build_plan +from examples.configuration import _recipe_command, check_report, init_main +from examples.lifecycle import Supervisor, stop_owned +from examples.setup_environment import environment_for_python, environment_path, recipe_mode ROOT = Path(__file__).resolve().parents[1] HOST = [sys.executable, "-c", "from embodirun.services.host.cli.cli import main; raise SystemExit(main())"] @@ -76,7 +81,21 @@ def output(self) -> Path: @property def python(self) -> str: - return str(self.resolve(self.data["python"])) if "python" in self.data else sys.executable + if "python" in self.data: + path = Path(self.data["python"]).expanduser() + return str((self.path.parent / path).absolute()) + if override := os.environ.get("EMBODIRUN_SCENE_PYTHON"): + return str(Path(shutil.which(override) or override).expanduser().absolute()) + if self.kind in {"snack", "microduck"}: + candidate = environment_path(self.kind) / "bin/python" + return str(candidate) + return sys.executable + + @property + def environment(self) -> Path: + if "python" not in self.data and not os.environ.get("EMBODIRUN_SCENE_PYTHON"): + return environment_path(self.kind) + return environment_for_python(self.python) def host(self, *args: str) -> list[str]: return [ @@ -165,19 +184,37 @@ def load_example(path: Path) -> Example: if "partition" in p: text(p["partition"], "partition") else: - from examples.xlerobot_snack_delivery.run import _load_json, _validate_config + from examples.xlerobot_snack_delivery.run import load_recipe_config - _validate_config(_load_json(example.resolve(p.get("config")))) + load_recipe_config(example.resolve(p.get("config")), example.resolve(p.get("deployment"))) services = _load_yaml(example.resolve(p.get("deployment"))) if not isinstance(services, dict) or not {"owner", "control", "model", "cameras"} <= services.keys(): raise ValueError("snack deployment needs owner, control, model, and cameras") + text(services["owner"].get("hardware_config"), "owner.hardware_config") + if not isinstance(services["model"], dict) or not isinstance(services["cameras"], dict): + raise ValueError("snack deployment model and cameras must be mappings") + text(services["model"].get("endpoint"), "model.endpoint") + if any(not isinstance(role, str) for role in services["cameras"].values()): + raise ValueError("snack deployment camera roles must be strings") return example -def commands(example: Example, action: str, output: Path) -> tuple[list[list[str]], dict[str, str]]: +def commands( + example: Example, action: str, output: Path, *, mode: str | None = None +) -> tuple[list[list[str]], dict[str, str]]: """Build argv without invoking a shell or importing optional GPU packages.""" p = example.parameters env: dict[str, str] = {} + if action == "setup" and example.kind in {"snack", "microduck"}: + command = [ + sys.executable, + str(ROOT / "examples/setup_environment.py"), + example.kind, + "--environment", + str(example.environment), + ] + command.extend(["--mode", recipe_mode(example.kind, mode)]) + return [command], env if example.kind == "rollout": if action == "setup": return [example.host("init"), example.host("sync", "--source", str(ROOT))], env @@ -205,8 +242,6 @@ def commands(example: Example, action: str, output: Path) -> tuple[list[list[str ], env elif example.kind == "snack": directory = ROOT / "examples/xlerobot_snack_delivery" - if action == "setup": - return [["bash", str(directory / "setup.sh")]], env if action == "up": return [ [ @@ -224,6 +259,8 @@ def commands(example: Example, action: str, output: Path) -> tuple[list[list[str str(directory / "run.py"), "--config", str(example.resolve(p["config"])), + "--deployment", + str(example.resolve(p["deployment"])), "--output", str(output / "result"), "--mode", @@ -276,7 +313,15 @@ def commands(example: Example, action: str, output: Path) -> tuple[list[list[str raise ValueError(f"{action} is not supported for {example.kind}; see examples/README.md") -def execute(argv: list[list[str]], env: dict[str, str], output: Path, *, parallel: bool, interactive: bool) -> None: +def execute( + argv: list[list[str]], + env: dict[str, str], + output: Path, + *, + parallel: bool, + interactive: bool, + supervisor: Supervisor | None = None, +) -> None: """Own and reap local children; preserve stdin for supervised handover.""" children: list[subprocess.Popen] = [] logs = [] @@ -290,9 +335,18 @@ def execute(argv: list[list[str]], env: dict[str, str], output: Path, *, paralle command, cwd=ROOT, env=env, stdout=log, stderr=subprocess.STDOUT, start_new_session=True ) children.append(child) - if not parallel and child.wait() != 0: - raise RuntimeError(f"command {index} failed; see {output}") + if not parallel: + while child.poll() is None: + if supervisor is not None and supervisor.cancelled.wait(0.1): + raise KeyboardInterrupt + if supervisor is None: + time.sleep(0.1) + if child.returncode != 0: + log_path = output / f"command-{index}.log" + raise RuntimeError(f"command {index} failed; inspect {output if interactive else log_path}") while True: + if supervisor is not None and supervisor.cancelled.is_set(): + raise KeyboardInterrupt # Poll every client: short-circuiting on the first live child can # hide a later client's failure until the first rollout finishes. codes = [child.poll() for child in children] @@ -310,29 +364,106 @@ def execute(argv: list[list[str]], env: dict[str, str], output: Path, *, paralle try: child.wait(timeout=45) except subprocess.TimeoutExpired: - os.killpg(child.pid, signal.SIGKILL) + with contextlib.suppress(ProcessLookupError): + os.killpg(child.pid, signal.SIGKILL) child.wait() + finally: + # Each command starts its own session. Its leader can exit + # before its descendants, including after a cleanup timeout. + # Finish that owned group even when the leader was already reaped. + with contextlib.suppress(ProcessLookupError): + os.killpg(child.pid, signal.SIGKILL) for log in logs: log.close() def main(argv: list[str] | None = None) -> int: + argv = list(sys.argv[1:] if argv is None else argv) + if argv and argv[0] == "init": + try: + return init_main(argv[1:]) + except (OSError, ValueError) as error: + print(f"example: {error}", file=sys.stderr) + return 2 parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("config", type=Path) parser.add_argument("command", choices=COMMANDS) parser.add_argument("--allow-hardware", action="store_true", help="permit hardware service startup or motion") + parser.add_argument( + "--mode", + help="setup/check: XLeRobot software/hardware (default hardware), MicroDuck software/simulation (default simulation)", + ) + parser.add_argument("--json", action="store_true", help="emit one machine-readable local check report") args = parser.parse_args(argv) + stop_error: RuntimeError | None = None + output: Path | None = None + report_path: Path | None = None + example: Example | None = None + mode: str | None = None try: - example = load_example(args.config) + if args.mode is not None and args.command not in {"setup", "check"}: + raise ValueError("--mode is available for setup and check") + if args.json and args.command != "check": + raise ValueError("--json is available for check") + if args.command == "down": + path = args.config.expanduser().resolve() + data = _load_yaml(path) + example = Example(path, data) + if example.kind not in {"rollout", "snack", "microduck"}: + raise ValueError("unknown recipe kind") + else: + example = load_example(args.config) + if args.mode is not None and example.kind not in {"snack", "microduck"}: + raise ValueError("--mode is available only for XLeRobot and MicroDuck recipe setup/check") + mode = recipe_mode(example.kind, args.mode) + if args.command == "down": + try: + stop_owned(example.output) + except RuntimeError as error: + if example.kind != "rollout": + raise + stop_error = error + print(f"Local launcher stop unconfirmed: {error}", file=sys.stderr) + if example.kind != "rollout": + print("Owned launcher cleanup finished. Verify physical stop feedback before leaving hardware.") + return 0 + if args.command == "check": + report = check_report(example, mode=mode) + if args.json: + print(json.dumps(report, indent=2)) + return 2 if report["issues"] else 0 + print(f"Stage: {report['mode']} local prerequisite check") + for item in report["issues"]: + print(f"Missing: {item['location']}: {item['message']}\nNext: {item['next_action']}", file=sys.stderr) + print("Not verified: " + "; ".join(report["unverified"])) + if report["issues"]: + print("Result: needs attention. Correct the listed inputs and repeat this check.", file=sys.stderr) + return 2 + if example.kind != "microduck" or mode == "software": + if example.kind == "microduck": + print( + "Result: software configuration/environment metadata passed; scene/CUDA/model was not tested." + ) + else: + print("Result: local prerequisites passed; live robot/model readiness was not tested.") + for action in report["next_actions"]: + print(f"Next: {action}") + return 0 if args.command == "validate": print(f"Valid example: {example.data['name']} (offline configuration check)") return 0 action = "run" if args.command == "plan" else args.command stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S.%fZ") output = example.output / f"{stamp}-{action}" - argv_list, overrides = commands(example, action, output) + argv_list, overrides = commands(example, action, output, mode=mode) if args.command == "plan": - print(json.dumps({"commands": argv_list, "environment": overrides, "output": str(output)}, indent=2)) + plan = {"commands": argv_list, "environment": overrides, "output": str(output)} + if example.kind == "snack": + plan["notes"] = [ + "Hardware command preview only; no process is started. Use dry-run for fixture rehearsal.", + "Actual up/run needs --allow-hardware after calibration and emergency-stop checks.", + ] + print(json.dumps(plan, indent=2)) return 0 if example.kind in {"rollout", "snack"} and action in {"up", "run"} and not args.allow_hardware: raise ValueError(f"{action} needs --allow-hardware after calibration and emergency-stop checks") @@ -340,7 +471,7 @@ def main(argv: list[str] | None = None) -> int: deployment_values = _load_yaml(example.resolve(example.parameters["deployment"])) if "REPLACE_" in repr(deployment_values): raise ValueError("replace the REPLACE_* values in the deployment YAML before setup/up/run") - if example.kind == "microduck": + if example.kind == "microduck" and action != "setup": for key in ("project_root", "inference_root", "checkpoint", "episodes", "manifest"): path = example.resolve(example.parameters[key]) if action == "provision" and key == "manifest": @@ -355,9 +486,21 @@ def main(argv: list[str] | None = None) -> int: env = {k: v for k, v in os.environ.items() if example.kind != "microduck" or not k.startswith("MICRODUCK_")} env.update(overrides) output.mkdir(parents=True, exist_ok=False, mode=0o700) + print(f"Stage: {action}" + (f" ({mode})" if action == "setup" else ""), flush=True) + if example.kind in {"snack", "microduck"}: + print(f"Scene Python: {example.python}", flush=True) print(f"Outputs: {output}", flush=True) + if example.kind == "snack" and action == "up": + print(f"Service logs: {example.output / 'services'}", flush=True) + elif not (example.kind == "snack" and action == "run"): + print(f"Command logs: {output / 'command-0.log'}", flush=True) report = {"example": example.data, "command": action, "commands": argv_list, "status": "running"} - report["revision"] = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip() + try: + report["revision"] = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=ROOT, text=True, stderr=subprocess.DEVNULL + ).strip() + except (OSError, subprocess.CalledProcessError): + report["revision"] = os.environ.get("EMBODIRUN_REVISION") inputs = [example.path] for key in ("deployment", "config", "manifest", "episodes"): if key in example.parameters: @@ -376,35 +519,90 @@ def main(argv: list[str] | None = None) -> int: report["input_snapshots"][str(path)] = str(snapshot.relative_to(output)) report_path = output / "run.json" report_path.write_text(json.dumps(report, indent=2) + "\n") - try: - execute( - argv_list, - env, - output, - parallel=example.kind == "rollout" and action == "run", - interactive=example.kind == "snack" and action in {"up", "run"}, - ) - report["status"] = "complete" - except BaseException: + with Supervisor(example.output, action) as supervisor: + try: + execute( + argv_list, + env, + output, + parallel=example.kind == "rollout" and action == "run", + interactive=example.kind == "snack" and action in {"up", "run"}, + supervisor=supervisor, + ) + report["status"] = "failed" if stop_error else "complete" + except BaseException: + report["status"] = "cancelled" if supervisor.cancelled.is_set() else "failed" + if example.kind == "rollout" and action in {"up", "run"}: + # Killing a Host client alone would leave remote motion running. + print("Stopping this example's deployment after failure/interruption.", flush=True) + result = subprocess.run(example.host("down"), cwd=ROOT, env=env, check=False) + report["cleanup_returncode"] = result.returncode + if result.returncode: + print( + "Shutdown failed: use the physical emergency stop and inspect the robot services.", + file=sys.stderr, + ) + raise + finally: + report_path.write_text(json.dumps(report, indent=2) + "\n") + print(f"Result: {report['status']}. Report: {report_path}", flush=True) + if action == "setup" and example.kind in {"snack", "microduck"}: + print(f"Next: {_recipe_command(example.path, 'check', '--mode', mode, '--json')}", flush=True) + if example.kind == "snack": + if action == "dry-run": + print("Software rehearsal finished; hardware execution and task success remain unverified.", flush=True) + print(f"Task result: {output / 'result/status.json'}", flush=True) + print( + "Next for robot preparation: " + _recipe_command(example.path, "setup", "--mode", "hardware"), + flush=True, + ) + elif action == "run": + print(f"Inspect task/physical evidence: {output / 'result/status.json'}", flush=True) + print("Verify physical stop feedback before leaving the robot.", flush=True) + return 2 if stop_error else 0 + except (OSError, ValueError, RuntimeError) as error: + if report_path is not None and report["status"] == "running": report["status"] = "failed" - if example.kind == "rollout" and action in {"up", "run"}: - # Killing a Host client alone would leave remote motion running. - print("Stopping this example's deployment after failure/interruption.", flush=True) - result = subprocess.run(example.host("down"), cwd=ROOT, env=env, check=False) - report["cleanup_returncode"] = result.returncode - if result.returncode: - print( - "Shutdown failed: use the physical emergency stop and inspect the robot services.", - file=sys.stderr, - ) - raise - finally: report_path.write_text(json.dumps(report, indent=2) + "\n") - return 0 - except (OSError, ValueError, RuntimeError) as error: + if args.command == "check" and args.json: + print( + json.dumps( + { + "recipe": str(args.config.expanduser().absolute()), + "kind": example.kind if example is not None else None, + "mode": mode if mode is not None else args.mode, + "status": "needs_attention", + "issues": [ + { + "code": "configuration_invalid", + "location": str(args.config), + "message": str(error), + "next_action": f"Correct the reported configuration/file error, then repeat check for {args.config}.", + } + ], + "verified": [], + "unverified": ["Configuration, dependencies, live services and physical readiness"], + "next_actions": [f"Correct the reported error, then repeat check for {args.config}."], + }, + indent=2, + ) + ) + return 2 print(f"example: {error}", file=sys.stderr) + if output is not None: + print(f"Result: failed. Logs and report: {output}", file=sys.stderr) + retry = [args.command] + if args.mode: + retry.extend(["--mode", args.mode]) + if args.allow_hardware: + retry.append("--allow-hardware") + print( + f"Next: correct the reported error, then retry {_recipe_command(args.config.absolute(), *retry)}", + file=sys.stderr, + ) return 2 except KeyboardInterrupt: + print(f"Result: interrupted. Inspect {output or args.config} before retrying.", file=sys.stderr) return 130 diff --git a/examples/setup_environment.py b/examples/setup_environment.py new file mode 100644 index 0000000..400b378 --- /dev/null +++ b/examples/setup_environment.py @@ -0,0 +1,172 @@ +"""Install the same Python profiles for native recipes and container images.""" + +from __future__ import annotations + +import argparse +import os +import shutil +import signal +import subprocess +import sys +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +PYTHON_VERSION = "3.12" +SNACK_PROJECT = ROOT / "examples/xlerobot_snack_delivery" +MICRODUCK_PROJECT = ROOT / "examples/microduck_vln" +PROFILES = { + "host": ".venv", + "snack": ".venv-xlerobot-snack", + "microduck": ".venv-microduck", +} +RECIPE_PROJECTS = {"snack": SNACK_PROJECT, "microduck": MICRODUCK_PROJECT} + + +class _SetupCancelled(Exception): + """Stop a setup command after a launcher cancellation signal.""" + + +def _cancel_setup(_signum: int, _frame: object) -> None: + raise _SetupCancelled + + +def _run_install(command: list[str], env: dict[str, str]) -> None: + process = subprocess.Popen(command, cwd=ROOT, env=env, start_new_session=True) + try: + code = process.wait() + if code: + raise subprocess.CalledProcessError(code, command) + except (_SetupCancelled, KeyboardInterrupt): + if os.name == "posix": + # The session belongs to this uv command, including build helpers. + for sig in (signal.SIGTERM, signal.SIGKILL): + try: + os.killpg(process.pid, sig) + except ProcessLookupError: + break + if sig == signal.SIGTERM: + time.sleep(1) + elif process.poll() is None: + process.terminate() + try: + process.wait(timeout=10) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + raise + + +def environment_path(kind: str) -> Path: + """Return the default isolated environment for a scene.""" + return ROOT / PROFILES[kind] + + +def environment_for_python(python: str) -> Path: + """Find a venv target without resolving its Python symlink to a base interpreter.""" + path = Path(python).expanduser() + if not path.is_absolute(): + path = Path(shutil.which(python) or python).absolute() + if path.parent.name != "bin" or path.name not in {"python", "python3", "python3.12"}: + raise ValueError("python must name a virtual environment's bin/python (or python3/python3.12)") + environment = path.parent.parent + if environment.exists() and not (environment / "pyvenv.cfg").is_file(): + raise ValueError(f"{environment} is not a virtual environment; choose a new venv directory or correct python") + return environment + + +def recipe_mode(kind: str, mode: str | None = None) -> str: + """Select the recipe's default profile and explain unsupported modes.""" + modes = {"snack": ("software", "hardware"), "microduck": ("software", "simulation")} + if kind not in modes: + if mode not in {None, "hardware"}: + raise ValueError("--mode is available only for XLeRobot and MicroDuck setup/check") + return "hardware" + choices = modes[kind] + selected = choices[-1] if mode is None else mode + if selected not in choices: + name = "MicroDuck" if kind == "microduck" else "XLeRobot" + raise ValueError(f"{name} setup/check mode must be {' or '.join(choices)} (default: {choices[-1]})") + return selected + + +def install_commands( + kind: str, environment: Path, *, minimal: bool = False, mode: str | None = None +) -> list[list[str]]: + """Install each profile from its own lock into the caller-selected environment.""" + if kind not in PROFILES: + raise ValueError(f"Unknown install profile: {kind}") + mode = recipe_mode(kind, mode) + uv = shutil.which("uv") + if uv is None: + raise RuntimeError("Install uv 0.12.x, then repeat setup; see docs/en/installation.md") + if kind in RECIPE_PROJECTS: + command = [ + uv, + "sync", + "--project", + str(RECIPE_PROJECTS[kind]), + "--python", + PYTHON_VERSION, + "--frozen", + "--no-default-groups", + ] + if mode != "software" and not minimal: + command.extend(["--extra", mode]) + return [command] + return [ + [ + uv, + "sync", + "--project", + str(ROOT), + "--python", + PYTHON_VERSION, + "--frozen", + "--no-default-groups", + "--group", + "host", + ] + ] + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("profile", choices=PROFILES) + parser.add_argument("--environment", type=Path) + parser.add_argument("--minimal", action="store_true", help="install only the profile's locked base dependencies") + parser.add_argument( + "--mode", + help="XLeRobot: software/hardware (default hardware); MicroDuck: software/simulation (default simulation)", + ) + args = parser.parse_args(argv) + if args.profile not in RECIPE_PROJECTS and args.mode is not None: + parser.error("--mode is available only for XLeRobot and MicroDuck profiles") + try: + mode = recipe_mode(args.profile, args.mode) + except ValueError as error: + parser.error(str(error)) + environment = (args.environment or environment_path(args.profile)).expanduser().absolute() + env = {**os.environ, "UV_PROJECT_ENVIRONMENT": str(environment)} + signal.signal(signal.SIGTERM, _cancel_setup) + try: + print(f"Stage: install {mode if not args.minimal else 'software'} environment", flush=True) + print(f"Python target: {environment / 'bin/python'}", flush=True) + print(f"Dependency lock: {RECIPE_PROJECTS.get(args.profile, ROOT) / 'uv.lock'}", flush=True) + for command in install_commands(args.profile, environment, minimal=args.minimal, mode=mode): + _run_install(command, env) + except (_SetupCancelled, KeyboardInterrupt): + print("Setup interrupted. Repeat setup with the same manifest to finish installation.", file=sys.stderr) + return 130 + except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error: + print(f"Setup failed: {error}. Correct the reported install error, then repeat setup.", file=sys.stderr) + return 2 + print(f"Environment ready: {environment / 'bin/python'}") + if args.profile == "snack" and mode == "hardware" and not args.minimal: + print(f"SDK directory for hardware.local.json sdk_src: {environment / 'lib/python3.12/site-packages'}") + print("Use this installed LeRobot source; calibration and device paths still need operator input.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/examples/xlerobot_snack_delivery/AGENT_GUIDE.md b/examples/xlerobot_snack_delivery/AGENT_GUIDE.md new file mode 100644 index 0000000..961f4ad --- /dev/null +++ b/examples/xlerobot_snack_delivery/AGENT_GUIDE.md @@ -0,0 +1,86 @@ +# XLeRobot deployment Agent guide + +这是普通仓库说明,可交给你现有的 coding Agent 读取。它帮助准备环境、配置和软件演练。 +RPent 仍负责运行时的任务规划;Astra 是可选的场景评审;EmbodiRun 负责观测、验证和执行; +XLeRobot owner 负责设备连接与停止。 + +## 可直接交给 Agent 的起始 prompt + +```text +请先读仓库 AGENTS.md、examples/xlerobot_snack_delivery/README.md 和 +examples/xlerobot_snack_delivery/AGENT_GUIDE.md,帮助我完成 XLeRobot 上手。 +目标 Linux 主机:。 +目标:<无设备软件演练,或准备我自己的小车;默认先做软件演练>。 +安装方式:。 +已有 checkout / 本地配置目录:<路径;可从当前工作区推断时直接复用>。 +复用 embodirun example CLI,保存每步命令、退出码和输出目录。 +根据 check --mode software|hardware --json 的诊断修复可推断的问题, +只向我询问真实设备、标定、相机、路线和模型等无法从文件确认的信息。 +本次只做准备与软件演练,不启动 owner/Control、连接设备、执行运动, +不自动标定、不自动确认。完成后报告验证范围、首次阻塞原因和下一步。 +``` + +## 执行步骤 + +1. **确定主机和阶段。** 先确认在哪台 Linux 主机安装,以及做软件演练还是硬件准备。 + 从已有 SSH 配置、checkout 和用户提供的路径推断可确定的信息,保留现有修改。 + 在目标主机检查 OS/架构、Python、uv 或 Docker/Compose;Mac 可用于编辑和远程操作, + 不在 Mac 上启动这些服务。软件阶段不需要设备、GPU、SDK、标定、路线录制或模型。 +2. **复用本地配置。** 先找已有 `example.local.yaml`,没有时再 `init xlerobot`。 + `init` 遇到已有目录退出 2;按提示继续使用现有文件,或用 `--output` 指定新目录。 + 不删除旧目录、不覆盖标定或已编辑配置。命令顺序沿用 [README](README.md)。 +3. **准备软件环境。** native 显式执行 `setup --mode software`,再执行 + `check --mode software --json` 和 `dry-run`。Docker 使用 `xlerobot-software`; + 镜像已有环境,无需再跑 `setup`。默认 native 环境是 `.venv-xlerobot-snack`; + native 配置已指定 `python` 时复用该环境,不另复制一套虚拟环境。 + native 配置迁入 Docker 时,顶层 `python` 优先于镜像的解释器设置,宿主路径不能直接复用。 + 先备份 manifest,再仅去掉 Docker 副本的 `python` override,保留其余配置, + 让镜像使用 `/opt/venv/bin/python`;切回 native 时使用原备份。 + Docker 副本留在原配置目录,例如 `example.docker.yaml`,保留 deployment/task 的相对引用; + 后续 Docker 命令将 manifest 参数替换为 `/workspace/xlerobot/example.docker.yaml`。 + 也可在新的 Docker 目录 `init` 后迁移 deployment/hardware/task/routes 与路径引用; + 保留原目录和标定文件。所有容器命令保持同一 Compose 项目和同一 UID/GID。 +4. **按诊断处理。** `check --json` 只读取本地配置,不连接服务或设备。 + 退出 0 / `status: "passed"` 表示这一层检查通过;退出 2 / `needs_attention` + 表示还有待办。逐项读取 `issues` 的 `code`、`location`、`message`、`next_action`。 + 能从路径或现有配置推断的就修复并重跑同一模式;缺少真实信息时,说明具体字段和原因再问用户。 + 同时读取 `verified`、`unverified`、`next_actions`,不要由静态通过推断设备已就绪。 +5. **准备自己的车。** 只有用户选择硬件准备时才运行 `setup --mode hardware` 和 + `check --mode hardware --json`。优先将 `hardware.local.json` 的 `sdk_src` 指向 + 诊断中 `environment.sdk_src` 给出的锁定 LeRobot 安装目录;Docker 对应 + `/opt/venv/lib/python3.12/site-packages`。自备 SDK checkout 的版本需单独确认。 + 根据 [guide.md](guide.md) 填写设备、标定、相机映射、真实路线、handover 和 model endpoint。 + managed 路径的 Control endpoint 从 deployment 派生;路线相对 task JSON 解析。 + 保持 `allow_motion: false`;不代填相机确认、不把 fixture 改成真实路线、 + 不设置自动确认,也不执行 `up` 或 `run --allow-hardware`。 + +`setup` / `check` 省略 `--mode` 时默认是 hardware,因此软件流程每次都显式写 software。 +硬件准备结束后,实际标定、相机图像、模型输出、现场急停和新鲜停止反馈仍由操作员核验。 + +## 安装失败和重试 + +保留失败命令、模式、退出码和 Python/uv 或 Docker 版本。 +`uv sync` / `docker compose build` 在 Recipe CLI 之前失败时,尚无 `Outputs:` 或 `run.json`; +把该命令的终端 stdout/stderr 保存到自己选择的日志文件并记录位置。 +Recipe `setup` 已打印 `Outputs:` 时,再保留其中的 `run.json` / `command-0.log`。 +先读最早的错误,分清网络下载、Python 版本、依赖安装与本地路径问题, +再使用 CLI 提示重试;复用选定环境和配置目录。 + +仅当日志明确报 uv HTTP read timeout 时,可用 +`UV_HTTP_TIMEOUT=300` 临时重试 native 的同一 setup 命令。 +不要为掩盖安装错误改锁文件或换一套未记录的依赖;宿主变量不会自动进入 Docker build。 +记录下载缓存是否为空、失败原因和重试次数,缓存重试成功不代表首次空缓存安装已完成。 + +## 交付结果 + +报告目标主机、阶段、安装方式、实际命令及退出码、修改的配置字段、诊断状态和输出路径。 +软件演练完成时说明 fixture 流程完成、未调用模型/任务 Agent/设备, +`task_success` 仍为 `unverified`、`physical_success` 为 null。 +硬件准备完成时列出剩余的操作员核验项;不声称模型兼容、真机运动或递送通过。 + +如以后经用户授权操作服务,Docker 的 `down` 必须使用与 launcher 相同的 Compose 项目和 UID/GID。 +共享 `/tmp` named volume 只让私有 launcher socket 在这些容器间可见,不是依赖缓存; +保留原有权限和清理确认。软件准备阶段仍在 `dry-run` 与报告后结束。 + +这份说明可由现有 Agent 使用;独立首次使用体验请填写 +[FIRST_USE_FEEDBACK.md](FIRST_USE_FEEDBACK.md),实际反馈需由试用者提供。 diff --git a/examples/xlerobot_snack_delivery/FIRST_USE_FEEDBACK.md b/examples/xlerobot_snack_delivery/FIRST_USE_FEEDBACK.md new file mode 100644 index 0000000..db561ed --- /dev/null +++ b/examples/xlerobot_snack_delivery/FIRST_USE_FEEDBACK.md @@ -0,0 +1,56 @@ +# XLeRobot first-use trial and feedback + +这份材料用于课堂试用或新用户第一次上手。当前提供的是流程和记录模板, +尚未收集真实同学反馈。请在可使用的 Linux 主机上只做无设备软件演练。 + +## 试用流程 + +1. 从 [README](README.md) 选择 native 或 Docker,记录开始时间、OS/架构和版本。 + 首次安装时记录下载缓存是否为空;不要清理共享缓存来制造首次安装。 +2. 依次完成 `init → validate → plan → setup --mode software → check --mode software --json → dry-run`。 + Docker 使用 `xlerobot-software`,镜像已有环境,跳过 `setup`。 + 遇到已有配置就复用它;native 转 Docker 前先备份 manifest,只去掉 Docker 副本的 + 顶层 `python` override,保留其他字段和标定,或另建 Docker 目录再迁配置。 + 不确定时记录问题和第一条阻塞错误。 +3. Recipe CLI 打印 `Outputs:` 后,记录其中的 `run.json`、`command-0.log`; + dry-run 执行后再查看 `result/status.json`。 + 若 `uv sync` / `docker compose build` 在 CLI 前失败,尚无这些文件, + 保存该命令的终端 stdout/stderr、完整命令、退出码和所选日志位置即可。 + 说明哪一步成功、哪一步仍未完成。可把 [Agent 起始 prompt](AGENT_GUIDE.md) + 交给自己的 coding Agent,再记录它问了什么、做了什么、在哪一步需要人帮助。 +4. 填写下面的短表并记录结束时间。软件演练到此结束,不启动 owner/Control, + 不运行 `up` 或 `run --allow-hardware`。 + +## 记录模板 + +```text +匿名试用编号: +日期、checkout 分支/commit: +目标主机 OS / 架构: +native 或 Docker;Python/uv 或 Docker/Compose 版本: +此前是否使用过 EmbodiRun / XLeRobot: +独立照 README / Agent 协助 / 同伴协助: +开始时间 / 结束时间 / 下载缓存状态: +实际命令与退出码: +到达的最后一步: +check 的 status 与首条 issue(code / location / next_action): +第一条阻塞错误及日志位置: +重试次数、解决方式和外部帮助: +bootstrap/build 终端日志,或 run.json / result/status.json 的位置和结果(未生成则写未生成): +尚未完成或仍不理解的部分: +``` + +可附经过脱敏的日志片段。不要提交认证文件、配对码、token、私人 IP/设备标识、 +真实路线/室内地图或含个人信息的画面。公开反馈使用匿名编号与必要的错误信息。 + +## 试用后问五个问题 + +- 你在哪里找到入口,能否判断该选软件演练还是准备小车,哪些前提现在不用准备? +- 你能否说清 `deployment.local.yaml`、`hardware.local.json`、`config.local.json` + 分别改什么;哪一处需要更具体的例子? +- 看过结果文件后,软件成功说明了什么;为什么此时还不能据此上真机? +- 你在哪里需要问人或缺少路径,`check --json` 的 `next_action` 能否帮助你继续? +- 若使用 Agent,它是否复用了当前目录、只问必要信息、清楚报告范围;哪一步仍需要人工帮助? + +整理反馈时区分首次成功、重试成功、需要协助和未完成,保留原始问题。 +课堂软件试用和 Agent 辅助记录不代表完整冷安装、模型质量或物理递送验证。 diff --git a/examples/xlerobot_snack_delivery/README.md b/examples/xlerobot_snack_delivery/README.md index cca2ed7..b0b2c37 100644 --- a/examples/xlerobot_snack_delivery/README.md +++ b/examples/xlerobot_snack_delivery/README.md @@ -1,59 +1,166 @@ # XLeRobot snack delivery -Connect base routes, an RPent/Astra scene review, VLA grasp proposals, and a -supervised handover through EmbodiRun. The robot owner holds the serial and -camera connections; the example coordinates tasks through Control. +Rehearse a delivery without devices, then prepare your own XLeRobot for a +supervised run. The task combines recorded base routes, RPent/Astra scene +review, π0.5 grasp proposals, and a calibrated handover. EmbodiRun coordinates +the task through Control; the robot owner holds the motor and camera connections. -## Start without hardware +| Your goal | Start here | What you need | +|---|---|---| +| Learn the task or try the deployment instructions | [Software rehearsal](#software-rehearsal) | A checkout and either Python/uv or Docker; no robot or GPU | +| Prepare your own cart | [Prepare your robot](#prepare-your-robot) | Its Linux host, SDK, calibration, cameras, routes, and a compatible model service | +| Let a coding Agent help with deployment | [Agent guide](AGENT_GUIDE.md) | The target host and which of the two goals you want | -From the repository root, after `uv sync --frozen`: +This recipe remains experimental. A rehearsal tests software sequencing; +model quality and physical delivery need separate evidence on your setup. + +## Software rehearsal + +Run these commands in the checkout on the target Linux host. Native setup uses +Python 3.12 and uv 0.12.x. Network access is needed for the first installation. ```bash -CONFIG=examples/xlerobot_snack_delivery/example.yaml -bash examples/run.sh "$CONFIG" validate -bash examples/run.sh "$CONFIG" plan -bash examples/run.sh "$CONFIG" dry-run +uv sync --frozen --python 3.12 +uv run --frozen embodirun example init xlerobot +CONFIG=examples/local/xlerobot/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" setup --mode software +uv run --frozen embodirun example "$CONFIG" check --mode software --json +uv run --frozen embodirun example "$CONFIG" dry-run ``` -The dry-run exercises task sequencing with fixture routes and feedback. It -does not start the owner, inference, or RPent. Inspect `result/status.json` -and `result/events.jsonl` in the printed output directory. +`init` creates linked local files under `examples/local/xlerobot/`. If that +directory already exists, it exits with code 2 and shows how to reuse its +`example.local.yaml`; use that path or choose a new `--output` directory. +Your edited files are not overwritten. -## Prepare your robot +`validate` checks configuration and `plan` prints the proposed command and +output path. `setup --mode software` prepares a small environment without the +owner or PyTorch. `check --mode software --json` should exit 0 with +`status: "passed"`. The dry-run uses route and feedback fixtures and starts no +owner, inference service, or RPent. + +The checkout's initial `uv sync` also installs its default development group; +the separate recipe software environment contains the smaller runtime dependencies. + +For a dry-run, the printed `Outputs:` directory contains `run.json`, `command-0.log`, and +`result/status.json` / `result/events.jsonl`. A completed fixture run keeps +`task_success: "unverified"` and `physical_success: null`. -Follow [setup and calibration](guide.md), then copy the local configuration: +The separate Recipe software workflow runs for relevant source, dependency or +container changes, and supports manual dispatch. It installs the software Recipe +from its frozen lock, builds the production +software image in a fresh Docker builder and runs both public entrypoints as +the caller UID. It checks printed commands outside the checkout with space-containing +paths, failure diagnostics, fixture results, launcher cleanup and native/container +package parity. To repeat the installed-environment checks: ```bash -cp examples/xlerobot_snack_delivery/example.yaml examples/xlerobot_snack_delivery/example.local.yaml -cp examples/xlerobot_snack_delivery/config.example.json examples/xlerobot_snack_delivery/config.local.json -cp examples/xlerobot_snack_delivery/deployment.example.yaml examples/xlerobot_snack_delivery/deployment.local.yaml -cp examples/xlerobot_snack_delivery/hardware.example.json examples/xlerobot_snack_delivery/hardware.local.json +"$PWD/.venv-xlerobot-snack/bin/python" scripts/check_recipe_software.py --recipe xlerobot ``` -Edit the local files: +Independent human first use, model compatibility and physical delivery remain +separate follow-up acceptance work. -- In the manifest, point `parameters.config` to `config.local.json` and - `parameters.deployment` to `deployment.local.yaml`. Set - `python: ../../.venv-xlerobot-snack/bin/python` for the hardware environment. -- In the deployment, set `owner.hardware_config: hardware.local.json` and the - running model service's endpoint and camera roles. -- In the hardware config, set SDK/device paths, calibration, and motion limits. - Confirm camera roles and stop feedback before enabling motion. -- In the task JSON, set your recorded routes, grasp instruction, agent factory, - and calibrated handover pose. Route paths resolve relative to that JSON. - The supplied zero-motion routes are fixtures and are rejected in hardware mode. +### Docker alternative -The GPU inference service is started separately; use a π0.5 checkpoint adapted -to XLeRobot's two-arm feature names and units. The [guide](guide.md) describes -route export, owner setup, and agent configuration. +Use Docker Engine and Compose on the target Linux host. This path needs no +device mapping, NVIDIA runtime, SDK, checkpoint, or host Python environment: + +```bash +mkdir -p examples/local +docker compose build xlerobot-software +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software init xlerobot --output /workspace/xlerobot +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml validate +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml plan +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml check --mode software --json +docker compose run --rm --user "$(id -u):$(id -g)" xlerobot-software /workspace/xlerobot/example.local.yaml dry-run +``` + +The image already contains the software environment, so skip native `setup`. +`/workspace/xlerobot` maps to `examples/local/xlerobot` in your checkout; +generated files belong to your host user. Both this image and native XLeRobot +setup use `examples/xlerobot_snack_delivery/uv.lock`; the hardware profile adds +the owner dependencies from that same project. See the +[installation guide](../../docs/en/installation.md) for dependency sources and +the hardware and GPU profiles. Linux ARM64 compatibility depends on the selected +integration and must be checked on the target; this software path does not test +a GPU or devices. + +If you reuse a native configuration in Docker, check the manifest's top-level +`python` field first: it takes priority over the image's interpreter setting. +A host virtual-environment path is not available inside the container. +Back up `example.local.yaml`, then remove only its `python` override from the +Docker copy so the image selects `/opt/venv/bin/python`; keep all other settings. +Keep that copy in the original configuration directory, for example as +`example.docker.yaml`, so relative deployment/task paths still resolve. In the +Docker commands above, replace the manifest argument with +`/workspace/xlerobot/example.docker.yaml`. +Use the native backup when returning to native execution. Alternatively, +`init` into a new directory such as `/workspace/xlerobot-docker` and migrate +your deployment, hardware, task, and route settings with their path references. +Keep the original configuration and calibration files. + +For container commands, use the same Compose project and host UID/GID each time. +Run from the same checkout, or pass the same explicit `-p NAME` for each command. +The services share a project-local `/tmp` volume for the launcher's private +control socket, enabling a later container's `down` to reach the active launcher. +This volume is launcher IPC storage; it does not cache dependencies. Wait for +cleanup confirmation and, for hardware, verify physical stop feedback. + +### If a step fails + +For `check`, exit code 2 means there is work to do. Read each issue's `location`, +`message`, and `next_action`, fix that input, then repeat the same check. +`verified` and `unverified` describe the scope of a successful check. + +If `uv sync` or `docker compose build` fails before the Recipe CLI starts, +there is no Recipe `Outputs:` directory or `run.json` yet. Save that command's +terminal stdout/stderr to a log file of your choice, with the exact command and +exit code. For Recipe `setup` failures that printed `Outputs:`, retain its +`run.json` and `command-0.log` there. Check the first error before retrying the +same setup mode. If it reports a uv HTTP read timeout, a native retry can use +`UV_HTTP_TIMEOUT=300` before the command. Record whether downloads were cached; +successful retries alone do not establish cold-install time. + +## Prepare your robot + +Continue with the same generated directory and +[setup and calibration](guide.md). Prepare real values before running the +hardware check: + +| File | Fill in for your setup | +|---|---| +| `deployment.local.yaml` | Model endpoint, Control ports, and camera feature-to-owner-role mapping | +| `hardware.local.json` | SDK/device paths, verified calibration, wheel geometry/direction, cameras, and motion limits | +| `config.local.json` and `routes/` | Your recorded directed routes, grasp instruction, planner factory, and calibrated handover pose | + +The supplied zero-motion routes are fixtures and are rejected for hardware. +Managed recipe runs derive Control endpoints from `deployment.local.yaml`; +route paths remain relative to the task JSON. Start inference separately with +a π0.5 checkpoint adapted to `lerobot.xlerobot.pi05`, its two-arm feature names, +units, and camera inputs. Installing the owner uses CPU PyTorch; it does not +install or validate the GPU model service. + +```bash +uv run --frozen embodirun example "$CONFIG" setup --mode hardware +uv run --frozen embodirun example "$CONFIG" check --mode hardware --json +``` + +Both commands default to hardware mode when `--mode` is omitted. Hardware +`check` reads local files and declarations. Before enabling motion, the +operator must verify actual calibration, camera images, model compatibility, +and physical stop feedback. Keep `allow_motion: false` during preparation; +the guide explains the operator's next steps. ## Run a supervised delivery +On the robot's Linux host, after those preparations and operator authorization: + ```bash -CONFIG=examples/xlerobot_snack_delivery/example.local.yaml -bash examples/run.sh "$CONFIG" setup -bash examples/run.sh "$CONFIG" validate -bash examples/run.sh "$CONFIG" up --allow-hardware +CONFIG=examples/local/xlerobot/example.local.yaml +uv run --frozen embodirun example "$CONFIG" up --allow-hardware ``` Keep `up` running in this terminal. It starts one owner and two scoped Control @@ -61,14 +168,16 @@ services. Confirm the physical stop through the owner UI, then in a second terminal run: ```bash -bash examples/run.sh examples/xlerobot_snack_delivery/example.local.yaml run --allow-hardware +CONFIG=examples/local/xlerobot/example.local.yaml +uv run --frozen embodirun example "$CONFIG" run --allow-hardware ``` Follow the prompts for arrival, grasp, and handover. Hardware confirmation is -never automated. Ctrl-C in the `up` terminal stops its service stack; check +never automated. Use `uv run --frozen embodirun example "$CONFIG" down` from another terminal +or Ctrl-C in the `up` terminal to stop its service stack; check the physical stop state before leaving the robot. -The default output root is `artifacts/examples/xlerobot-snack-delivery/`. +The generated example's output root is `examples/local/xlerobot/runs/`. `services/` holds private owner/Control configs and service logs; timestamped task directories contain `run.json` and `result/` with events, status, agent decisions, and proposals. Keep generated credentials local. @@ -78,4 +187,6 @@ decisions, and proposals. Keep generated credentials local. - [Setup and calibration](guide.md) - [Hardware and software](hardware.md) - [Agent and runtime interfaces](architecture.md) +- [Deployment Agent instructions](AGENT_GUIDE.md) +- [Classroom first-use trial and feedback](FIRST_USE_FEEDBACK.md) - [Shared manifest and launcher](../README.md) diff --git a/examples/xlerobot_snack_delivery/example.yaml b/examples/xlerobot_snack_delivery/example.yaml index 07f5b70..843ea5f 100644 --- a/examples/xlerobot_snack_delivery/example.yaml +++ b/examples/xlerobot_snack_delivery/example.yaml @@ -1,8 +1,7 @@ version: 1 name: xlerobot-snack-delivery kind: snack -# Omit python to use the Host interpreter for software-only dry-runs. -# Set this for hardware: python: ../../.venv-xlerobot-snack/bin/python +# setup prepares .venv-xlerobot-snack; the launcher selects it automatically. output_dir: ../../artifacts/examples/xlerobot-snack-delivery parameters: config: config.example.json diff --git a/examples/xlerobot_snack_delivery/guide.md b/examples/xlerobot_snack_delivery/guide.md index 28aef5b..2403563 100644 --- a/examples/xlerobot_snack_delivery/guide.md +++ b/examples/xlerobot_snack_delivery/guide.md @@ -2,29 +2,41 @@ [Demo overview](README.md) · [Hardware and software](hardware.md) · [Agent interfaces](architecture.md) +首次使用先走 [README 的软件演练](README.md#software-rehearsal): +`init → validate → plan → setup --mode software → check --mode software --json → dry-run`。 +Docker 使用 `xlerobot-software`,镜像已有同份 Recipe 锁中的软件依赖,跳过 `setup`。 +该路径不需要真实 SDK、标定、相机、路线、model endpoint 或 GPU,也不启动 owner/RPent。 +下面继续说明在目标 Linux 主机准备自己的小车。 + ## Bring-up and calibration 1. 按 [XLeRobot 官方装配资料](https://github.com/Vector-Wangel/XLeRobot) 搭建双轮底盘、双 SO-101 机械臂、供电和急停,安装前视、左右腕相机。硬件选择和预算见 [清单](hardware.md)。 -2. 复制 [hardware.example.json](hardware.example.json),填写稳定串口名、相机路径、SDK 路径、标定文件、轮向、轮径和轮距。保持 `allow_motion: false` 完成读数检查。确认相机角色、关节单位、限位和停止反馈后再开启运动;不要直接照搬示例轮向和递交姿态。 -3. 在机器人 Linux 主机运行 `bash examples/run.sh examples/xlerobot_snack_delivery/example.yaml setup`。安装器用项目 `uv.lock` 安装核心和 Host 依赖;硬件 SDK、相机和录制 extras 由独立 owner 包安装。仅做离线演练时,使用 Host 环境即可。 -4. 在 GPU 主机启动与该机器人训练配置匹配的 π0.5 服务,参考 [模型部署示例](../../configs/pi05/bi-so101-embodiinfer.yaml)。这里复用的是服务启动方式;本 recipe 的 binding 是 `lerobot.xlerobot.pi05`。模型须输出带 feature names 的本机标定绝对位置:关节 degrees、夹爪 range_0_100,左右各六维。通用权重或匿名 50×12 数组不能自动视为适配完成。 -5. 复制 [deployment.example.yaml](deployment.example.yaml),填写硬件配置路径、模型服务 endpoint 和 owner 相机角色。模型 endpoint 可经 SSH 转发到机器人主机。底盘和双臂 Control 默认分别使用 `127.0.0.1:8100`、`127.0.0.1:8101`;owner 使用 `8766`,提供不同的 `/robot/*` API。 +2. 复用软件演练生成的 `examples/local/xlerobot/`。未生成时,在仓库根目录运行 `uv run --frozen embodirun example init xlerobot`;已有目录会退出 2 并提示复用路径,不会覆盖。三份配置的分工:`deployment.local.yaml` 管 endpoint/端口/相机映射,`hardware.local.json` 管设备/标定/运动参数,`config.local.json` 管任务/路线/规划器/递交姿态。 +3. 在机器人 Linux 主机运行 `uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml setup --mode hardware`。Python 3.12 环境按 `examples/xlerobot_snack_delivery/pyproject.toml` 与 `uv.lock` 安装完整 owner extra,包括 CPU PyTorch、LeRobot、相机和录制依赖;Docker `xlerobot` 使用同一锁。依赖安装完成还需要填写设备配置,模型推理服务单独准备。 +4. 编辑 `hardware.local.json`,填写稳定串口名、相机路径、标定文件、轮向、轮径和轮距。`sdk_src` 优先填安装器或 `check --json` 中 `environment.sdk_src` 给出的 LeRobot 包父目录(默认位于 `.venv-xlerobot-snack/lib/python3.12/site-packages`,Docker 是 `/opt/venv/lib/python3.12/site-packages`)。owner 只从这个目录导入 SDK;使用自备 SDK checkout 时,其版本不由 Recipe 锁保证。保持 `allow_motion: false` 准备配置和读数检查。确认实际标定、相机角色、关节单位、限位和停止反馈后,由操作员决定何时开启运动;不要照搬示例轮向和递交姿态。 +5. 在 GPU 主机启动与该机器人训练配置匹配的 π0.5 服务,参考 [模型部署示例](../../configs/pi05/bi-so101-embodiinfer.yaml)。这里复用的是服务启动方式;本 recipe 的 binding 是 `lerobot.xlerobot.pi05`,仅控制双臂。模型须输出具名的本机标定绝对位置:左右各六维,五个关节为 degrees、夹爪为 range_0_100。两侧字段分别是 `left_arm_` / `right_arm_` 加上 `shoulder_pan.pos`、`shoulder_lift.pos`、`elbow_flex.pos`、`wrist_flex.pos`、`wrist_roll.pos`、`gripper.pos`。`feature_names` 必须覆盖这 12 个字段;通用权重或匿名 50×12 数组不能自动视为适配完成。 +6. 编辑 `deployment.local.yaml`,填写模型服务 endpoint 和相机映射。模型 endpoint 可经 SSH 转发到机器人主机。默认将 `observation.images.front`、`observation.images.left_wrist`、`observation.images.right_wrist` 映射到 owner 的 `front`、`left_wrist`、`right_wrist` 角色;它们必须与训练输入和实际图像一致。底盘和双臂 Control 默认分别使用 `127.0.0.1:8100`、`127.0.0.1:8101`;owner 使用 `8766`,提供不同的 `/robot/*` API。 + +运行 `check --mode hardware --json`,根据 `issues` 的 `location` / `next_action` 处理缺项。 +退出 0 的 `passed` 只表示本地声明与路径检查通过,实际设备连接、标定有效性、相机图像、 +模型输出、新鲜观测和停止反馈仍未验证。省略 `--mode` 的 `setup` / `check` 默认是 hardware。 先生成并校验配置,不启动硬件: ```bash .venv-xlerobot-snack/bin/python examples/xlerobot_snack_delivery/services.py \ - --deployment examples/xlerobot_snack_delivery/deployment.local.yaml \ - --state-dir artifacts/examples/xlerobot-snack-delivery/services --write-only + --deployment examples/local/xlerobot/deployment.local.yaml \ + --state-dir examples/local/xlerobot/runs/services --write-only ``` +完成下文的 RPent、路线和 handover 配置,并由操作员授权后, 启动唯一 owner 与两个 Control 服务,在该终端保持运行: ```bash -bash examples/run.sh examples/xlerobot_snack_delivery/example.local.yaml up --allow-hardware +uv run --frozen embodirun example examples/local/xlerobot/example.local.yaml up --allow-hardware ``` -`up` 自动生成仅本机可读的 owner 认证文件和 Control 配置。生成文件及日志默认放在 `artifacts/examples/xlerobot-snack-delivery/services/`。端口已被占用时命令拒绝启动,不会替换已有 owner。退出后检查急停和 owner 的停止反馈。 +`up` 自动生成仅本机可读的 owner 认证文件和 Control 配置。生成文件及日志默认放在 `examples/local/xlerobot/runs/services/`。端口已被占用时命令拒绝启动,不会替换已有 owner。退出后检查急停和 owner 的停止反馈。 owner 页面使用 `service-0.log` 中的短期配对码登录,无需填写 API token。通过 owner 的显式停止操作取得已确认的停止反馈,再开始 recipe。仅观察到轮速为零不会清除 `stop_unconfirmed`。owner 的生命周期和操作入口见 [owner 文档](../../integrations/xlerobot_owner/README.md)。 @@ -32,7 +44,7 @@ owner 页面使用 `service-0.log` 中的短期配对码登录,无需填写 AP [config.example.json](config.example.json) 的 `agent.factory` 指向 `agents.rpent.snack_agent:create_agent`。这个 RPent 侧适配器使用已有登录状态的 Codex/Astra,读取 Control 中同一个 observation 的图像,给出 `proceed` / `hold`、VLA 指令和短段步数;无相机、超时、错误身份或非法输出均停止。 -把 `agent.options.codex_bin` 改为运行主机上可用的 Codex 路径。环境需事先具有 Astra 的访问权限;recipe 不接收 API key 或 token。若完整 RPent 服务在另一台主机,把 `agent.factory` 指向自己的 `module:create_agent`,并在该工厂中连接 RPent。接口及字段见 [架构说明](architecture.md)。 +把 `agent.options.codex_bin` 改为运行主机上可用的 Codex 路径。环境需事先具有 Astra 的访问权限;recipe 不接收 API key 或 token。若完整 RPent 服务在另一台主机,把 `agent.factory` 指向自己的 `module:create_agent`,并在该工厂中连接 RPent。接口及字段见 [架构说明](architecture.md)。帮助安装配置的 coding Agent 可读 [部署指引](AGENT_GUIDE.md),运行时的 RPent/Astra 角色保持如上。 Agent 决策之后,recipe 等待一份更新的观测,再调用 VLA;VLA 返回后复查原观测的 freshness。推理超过 Control 的观测期限会停止,不会给旧动作换上新 observation_id。应通过模型部署优化满足该期限。 @@ -79,17 +91,26 @@ RPent 根据图和片段注释选择去程、返程的片段序列;无法判 ## 运行场景 -复制 recipe 配置,填写两个 Control endpoint、自己的路线、RPent 工厂和本机标定的 `handover.forward_pose` / `gripper_opening`。安装与脚本从仓库根目录执行: +编辑 `init` 生成的配置,填写自己的路线、RPent 工厂和本机标定的 +`handover.forward_pose` / `gripper_opening`。managed 入口从 `deployment.local.yaml` 的 Control +端口派生 endpoint,改端口只需改 deployment。独立调用 `run.py --config` 仍支持 task 中 +显式的 `control.endpoints`,也可用 `--deployment` 指定 deployment;路线继续相对 task JSON 解析。 +在目标 Linux 主机从仓库根目录执行: ```bash -CONFIG=examples/xlerobot_snack_delivery/example.local.yaml -bash examples/run.sh "$CONFIG" validate -bash examples/run.sh "$CONFIG" plan -bash examples/run.sh "$CONFIG" dry-run -bash examples/run.sh "$CONFIG" run --allow-hardware +CONFIG=examples/local/xlerobot/example.local.yaml +uv run --frozen embodirun example "$CONFIG" validate +uv run --frozen embodirun example "$CONFIG" plan +uv run --frozen embodirun example "$CONFIG" check --mode hardware --json +uv run --frozen embodirun example "$CONFIG" dry-run ``` +本地检查通过后,继续上面的 `up --allow-hardware`,保持该终端运行。 +操作员确认实际标定、相机/模型和现场停止反馈后,在第二个终端执行 +`uv run --frozen embodirun example "$CONFIG" run --allow-hardware`。 + - `validate` 校验配置,`plan` 预览运行命令与输出路径,均不连接设备、模型或 RPent。 +- `check --mode hardware --json` 列出本地配置的环境、SDK、标定、相机角色与路线缺项;不启动硬件,也不能证明停止反馈或模型已就绪。软件演练使用 `--mode software`,不要求真实设备前提。 - `dry-run` 使用标明 fixture 的状态和动作,只演练任务顺序。它不调用 Astra,也不证明模型或硬件可用。 - `run --allow-hardware` 先检查两个 Control 服务的 runtime、scope 和能力,然后执行“去程 → RPent 判断 → VLA 短段抓取 → 持物确认 → 返程 → 伸臂 → 松夹爪 → 接收确认”。人工确认之后会重新观测;只允许 dry-run 自动确认。 diff --git a/examples/xlerobot_snack_delivery/pyproject.toml b/examples/xlerobot_snack_delivery/pyproject.toml new file mode 100644 index 0000000..990d454 --- /dev/null +++ b/examples/xlerobot_snack_delivery/pyproject.toml @@ -0,0 +1,34 @@ +[project] +name = "embodirun-xlerobot-snack-recipe" +version = "0.1.0" +description = "Locked software rehearsal and local XLeRobot owner environment" +requires-python = ">=3.12,<3.13" +dependencies = [ + "embodirun", + "paramiko>=3.4,<5", + "PyYAML>=6,<7", + "rich>=13,<15", +] + +[project.optional-dependencies] +hardware = [ + "embodirun-xlerobot-owner[hardware,camera,recording,web]", + "torch", + "torchvision", +] + +[tool.uv] +package = false +required-version = ">=0.12.0,<0.13" +default-groups = [] + +[tool.uv.sources] +embodirun = { path = "../..", editable = true } +embodirun-xlerobot-owner = { path = "../../integrations/xlerobot_owner", editable = true } +torch = [{ index = "pytorch-cpu", marker = "sys_platform == 'linux'" }] +torchvision = [{ index = "pytorch-cpu", marker = "sys_platform == 'linux'" }] + +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true diff --git a/examples/xlerobot_snack_delivery/run.py b/examples/xlerobot_snack_delivery/run.py index 4ad682e..d34b369 100644 --- a/examples/xlerobot_snack_delivery/run.py +++ b/examples/xlerobot_snack_delivery/run.py @@ -114,6 +114,27 @@ def _load_json(path: Path) -> dict[str, Any]: return value +def load_recipe_config(path: Path, deployment: Path | None = None) -> dict[str, Any]: + """Resolve managed Control addresses while keeping routes beside the task file.""" + config = _load_json(path) + _reject_credentials(config) + if deployment is not None: + from embodirun.deployment.config.loader import _load_yaml + + try: + from .services import control_endpoints + except ImportError: + from services import control_endpoints + + values = _load_yaml(deployment) + if not isinstance(config.get("control"), Mapping): + raise RecipeError("control configuration must be an object") + config["control"] = {**config["control"], "endpoints": control_endpoints(values)} + _validate_config(config) + config["_config_dir"] = str(path.parent.resolve()) + return config + + def _write_json(path: Path, value: Mapping[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) temporary = path.with_suffix(path.suffix + ".tmp") @@ -1295,6 +1316,9 @@ def _validate_config(config: Mapping[str, Any]) -> None: navigation = config.get("navigation") if not isinstance(navigation, Mapping): raise RecipeError("navigation configuration is required") + routes = navigation.get("routes") + if not isinstance(routes, Mapping) or not {"outbound", "return"} <= routes.keys(): + raise RecipeError("navigation.routes must configure outbound and return") _positive(navigation.get("control_hz", 15), "navigation.control_hz") ConfigEvidenceNormalizer.from_config(config) grasp = config.get("grasp", {}) @@ -1326,6 +1350,7 @@ def _urlsplit(endpoint: str) -> Any: def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--config", type=Path, required=True) + parser.add_argument("--deployment", type=Path, help="derive local scoped Control addresses from this deployment") parser.add_argument("--output", type=Path, required=True) parser.add_argument("--mode", choices=("plan", "dry-run", "hardware"), default="plan") parser.add_argument( @@ -1335,10 +1360,7 @@ def main(argv: Sequence[str] | None = None) -> int: ) args = parser.parse_args(argv) try: - config = _load_json(args.config) - _validate_config(config) - config = dict(config) - config["_config_dir"] = str(args.config.parent.resolve()) + config = load_recipe_config(args.config, args.deployment) runtimes: RuntimeSet = DryRunRuntimes(config) if args.mode in {"plan", "dry-run"} else ControlRuntimes(config) runner = SnackDelivery( config, diff --git a/examples/xlerobot_snack_delivery/services.py b/examples/xlerobot_snack_delivery/services.py index 4f63359..e5dd4a4 100644 --- a/examples/xlerobot_snack_delivery/services.py +++ b/examples/xlerobot_snack_delivery/services.py @@ -30,10 +30,26 @@ def _private(path: Path, text: str) -> None: stream.write(text) -def prepare(config: dict[str, Any], directory: Path, *, base_dir: Path) -> list[list[str]]: +def control_endpoints(config: dict[str, Any]) -> dict[str, dict[str, str]]: + """Derive the task's scoped local endpoints from the service deployment.""" + if not isinstance(config, dict): + raise ValueError("snack deployment must be a mapping") + for section, fields in (("owner", ("port",)), ("control", ("base_port", "manipulation_port"))): + values = config.get(section) + if not isinstance(values, dict) or any(field not in values for field in fields): + raise ValueError(f"deployment.{section} must configure {', '.join(fields)}") ports = [config["owner"]["port"], config["control"]["base_port"], config["control"]["manipulation_port"]] - if len(set(ports)) != 3 or any(isinstance(p, bool) or not isinstance(p, int) or not 1 <= p <= 65535 for p in ports): + if any(isinstance(p, bool) or not isinstance(p, int) or not 1 <= p <= 65535 for p in ports) or len(set(ports)) != 3: raise ValueError("owner, base and manipulation ports must be distinct valid ports") + return { + "base": {"endpoint": f"http://127.0.0.1:{ports[1]}", "runtime_id": "xlerobot-base", "scope": "base"}, + "manipulation": {"endpoint": f"http://127.0.0.1:{ports[2]}", "runtime_id": "xlerobot-pi05", "scope": "arms"}, + } + + +def prepare(config: dict[str, Any], directory: Path, *, base_dir: Path) -> list[list[str]]: + control_endpoints(config) + ports = [config["owner"]["port"], config["control"]["base_port"], config["control"]["manipulation_port"]] hardware = Path(config["owner"]["hardware_config"]) hardware = hardware if hardware.is_absolute() else base_dir / hardware if not hardware.is_file(): @@ -152,6 +168,7 @@ def main(argv=None) -> int: logs.append(log) children.append(subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT)) print("Owner and Control processes started. Logs:", args.state_dir, flush=True) + print("Local process startup does not verify device, model or stop readiness.", flush=True) print("Use the owner UI to confirm a physical stop before running the recipe.", flush=True) while True: if any(child.poll() is not None for child in children): diff --git a/examples/xlerobot_snack_delivery/setup.sh b/examples/xlerobot_snack_delivery/setup.sh index 2ec6eda..a726cbc 100644 --- a/examples/xlerobot_snack_delivery/setup.sh +++ b/examples/xlerobot_snack_delivery/setup.sh @@ -1,43 +1,5 @@ #!/usr/bin/env bash set -euo pipefail - repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -venv_dir="${XLR_SNACK_VENV:-$repo_root/.venv-xlerobot-snack}" -install_owner=1 - -if [[ "${1:-}" == "--minimal" ]]; then - install_owner=0 - shift -fi -if [[ "$#" -ne 0 ]]; then - echo "usage: $0 [--minimal]" >&2 - exit 2 -fi - -if [[ ! -d "$venv_dir" ]]; then - python3 -m venv "$venv_dir" -fi -recipe_python="$venv_dir/bin/python" -if [[ ! -x "$recipe_python" ]]; then - echo "virtual environment Python is missing: $recipe_python" >&2 - exit 1 -fi - -bootstrap_dir="$(mktemp -d "${TMPDIR:-/tmp}/xlerobot-setup.XXXXXX")" -trap 'rm -rf "$bootstrap_dir"' EXIT -python3 -m venv "$bootstrap_dir" -"$bootstrap_dir/bin/python" -m pip install 'uv>=0.12,<0.13' -setup_uv="$bootstrap_dir/bin/uv" -UV_PROJECT_ENVIRONMENT="$venv_dir" "$setup_uv" sync \ - --project "$repo_root" --frozen --no-default-groups --group host -if [[ "$install_owner" -eq 1 ]]; then - "$setup_uv" pip install --python "$recipe_python" \ - -e "$repo_root/integrations/xlerobot_owner[hardware,camera,recording,web]" -fi - -echo "Recipe environment ready: $recipe_python" -if [[ "$install_owner" -eq 1 ]]; then - echo "Owner extras installed. Use the recipe services command to start one owner and two Control services." -else - echo "Minimal mode installed; use --mode plan or --mode dry-run only." -fi +exec python3 "$repo_root/examples/setup_environment.py" snack \ + --environment "${XLR_SNACK_VENV:-$repo_root/.venv-xlerobot-snack}" "$@" diff --git a/examples/xlerobot_snack_delivery/uv.lock b/examples/xlerobot_snack_delivery/uv.lock new file mode 100644 index 0000000..24e2218 --- /dev/null +++ b/examples/xlerobot_snack_delivery/uv.lock @@ -0,0 +1,1402 @@ +version = 1 +revision = 3 +requires-python = "==3.12.*" +resolution-markers = [ + "sys_platform == 'linux'", + "sys_platform != 'linux'", +] + +[[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.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" }, + { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" }, + { url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" }, + { url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" }, + { url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" }, + { url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" }, + { url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" }, + { url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" }, + { url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions" }, +] +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 = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions" }, +] +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 = "av" +version = "15.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/c3/83e6e73d1592bc54436eae0bc61704ae0cff0c3cfbde7b58af9ed67ebb49/av-15.1.0.tar.gz", hash = "sha256:39cda2dc810e11c1938f8cb5759c41d6b630550236b3365790e67a313660ec85", size = 3774192, upload-time = "2025-08-30T04:41:56.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/58/de78b276d20db6ffcd4371283df771721a833ba525a3d57e753d00a9fe79/av-15.1.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:40c5df37f4c354ab8190c6fd68dab7881d112f527906f64ca73da4c252a58cee", size = 21760991, upload-time = "2025-08-30T04:40:00.801Z" }, + { url = "https://files.pythonhosted.org/packages/56/cc/45f85775304ae60b66976360d82ba5b152ad3fd91f9267d5020a51e9a828/av-15.1.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:af455ce65ada3d361f80c90c810d9bced4db5655ab9aa513024d6c71c5c476d5", size = 26953097, upload-time = "2025-08-30T04:40:03.998Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f8/2d781e5e71d02fc829487e775ccb1185e72f95340d05f2e84eb57a11e093/av-15.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86226d2474c80c3393fa07a9c366106029ae500716098b72b3ec3f67205524c3", size = 38319710, upload-time = "2025-08-30T04:40:07.701Z" }, + { url = "https://files.pythonhosted.org/packages/ac/13/37737ef2193e83862ccacff23580c39de251da456a1bf0459e762cca273c/av-15.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:11326f197e7001c4ca53a83b2dbc67fd39ddff8cdf62ce6be3b22d9f3f9338bd", size = 39915519, upload-time = "2025-08-30T04:40:11.066Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e8032c7b8f2a4129a03f63f896544f8b7cf068e2db2950326fa2400d5c47/av-15.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a631ea879cc553080ee62874f4284765c42ba08ee0279851a98a85e2ceb3cc8d", size = 40286166, upload-time = "2025-08-30T04:40:14.561Z" }, + { url = "https://files.pythonhosted.org/packages/e2/23/612c0fd809444d04b8387a2dfd942ccc77829507bd78a387ff65a9d98c24/av-15.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8f383949b010c3e731c245f80351d19dc0c08f345e194fc46becb1cb279be3ff", size = 41150592, upload-time = "2025-08-30T04:40:17.951Z" }, + { url = "https://files.pythonhosted.org/packages/15/74/6f8e38a3b0aea5f28e72813672ff45b64615f2c69e6a4a558718c95edb9f/av-15.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d5921aa45f4c1f8c1a8d8185eb347e02aa4c3071278a2e2dd56368d54433d643", size = 31336093, upload-time = "2025-08-30T04:40:21.393Z" }, +] + +[[package]] +name = "bcrypt" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, + { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, + { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, + { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, + { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, + { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "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/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" }, +] + +[[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 = "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/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 = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "cmake" +version = "4.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/17/f8f42ae205604319cc36f46d9929bd9bfbd83d3d02d6314c44fa97c42006/cmake-4.1.3.tar.gz", hash = "sha256:89f48ddc2570eb62447e33311cffc6dfeb09631bd0a19423d8a59cec8af030f1", size = 34998, upload-time = "2025-11-19T22:41:27.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/79/1bf4009d7ef16d62e0b92ddb78efeda830ca5903149abf9dc01d270c3d4e/cmake-4.1.3-py3-none-macosx_10_10_universal2.whl", hash = "sha256:3b6b25ce8fecc768881b36a1dfbca0013adac10a299c73e24cf4cbb99e4c37d6", size = 49246088, upload-time = "2025-11-19T22:40:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9d/14e076406388efa2bbea2366ec0bbe85e2536787ebbb374dda792f068222/cmake-4.1.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893eb9c20d8a8bac3d951bbef9a4ce9d5495cd35a08b4e08d76215f5ead5897", size = 30381441, upload-time = "2025-11-19T22:40:31.55Z" }, + { url = "https://files.pythonhosted.org/packages/f7/9e/0f7216dfef03f1cbac0cdf4685da6994559f5ede3452e563335a35d6a6cb/cmake-4.1.3-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:487faf892ff5e05084c6a7f229dd9e568d0542b88487386acb42f0cb2f6634b6", size = 30781002, upload-time = "2025-11-19T22:40:35.325Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2e/69d9b1eee7b7c68e9ce53f8449e372151b4967c223ecd43c7083a4dece8d/cmake-4.1.3-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3dbddc52f839df0ebc1c6b6915bd78d63d0805137c6f419fbddd587404276c28", size = 32613762, upload-time = "2025-11-19T22:40:39.488Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9b/deac4d6f8cf4adcaa61d7f16d1ec42d41d471bf330ffcdac4d29c83e46a3/cmake-4.1.3-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b42e99eb6e976f455f29283dd7583270d611b55c7687b5fe8d022d9ae7c95de5", size = 28577197, upload-time = "2025-11-19T22:40:42.517Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6c/323c40671c6f1b3e02bb4a7404fbe2bf653190a56e63cf4b6a4f06e876bc/cmake-4.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:81f11b72bc59cbe547d9f283487ef0519bf68176edffcdfa1a4dc5a52f292369", size = 29690899, upload-time = "2025-11-19T22:40:45.363Z" }, + { url = "https://files.pythonhosted.org/packages/19/a3/ab7866f55ee11a07aa446ee31b91b8f337f1b702b9546fc7b18e23d0566f/cmake-4.1.3-py3-none-manylinux_2_31_armv7l.whl", hash = "sha256:fd633c4395b1522caedf0b64034d1a48ea0e483f19e9c2985d14ee7152b21593", size = 26522320, upload-time = "2025-11-19T22:40:48.463Z" }, + { url = "https://files.pythonhosted.org/packages/db/21/a99ed3f1192c85d6d565e61c0cd0161f8046afcf0b0951e6492be632f2f2/cmake-4.1.3-py3-none-manylinux_2_35_riscv64.whl", hash = "sha256:ea40a64b8027f2b7fb1684312a2f170e4d0904b7a4f123cd96e7290103bb1ed4", size = 28869263, upload-time = "2025-11-19T22:40:51.618Z" }, + { url = "https://files.pythonhosted.org/packages/13/66/3c32bb2d5e72f00a0861066b29cc6981cbffcf9786f7339317f151a4d4be/cmake-4.1.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e3782d5f82e8960290e50747b1fb5ff8396363a656ad5716a3aedc77334ca94f", size = 41751469, upload-time = "2025-11-19T22:40:54.75Z" }, + { url = "https://files.pythonhosted.org/packages/05/60/922c05d62ba5b422afd211966877673ddceb634e95552893bf9a11cc4e58/cmake-4.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:44b011b8374aac8f3d7a7fb319b3c25d54c2fd9342d94a855ae3a64240efe828", size = 35040544, upload-time = "2025-11-19T22:40:57.669Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/957336b0489f7d3050cd19010585d4ab5ebcdef485292b9baee68ebbeccf/cmake-4.1.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f29e924fd6d1a4f2f731eb743cc687b82063f73f15f0b4fb8e2b8a8211faba8", size = 45811680, upload-time = "2025-11-19T22:41:01.124Z" }, + { url = "https://files.pythonhosted.org/packages/85/0d/41e2ac694b156b249bfaccec071897c46b21deeb4db1ec51d949e7843f4b/cmake-4.1.3-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:466cdce904392f18b201471a3a6429cc12b4d98a166faa3ee0ad4461f3043083", size = 45859079, upload-time = "2025-11-19T22:41:04.694Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/42ca38f001b1f1327c19734e4c0080557a7991db832aacfe4b193ba7743a/cmake-4.1.3-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:d37db26f98ac26f0858cf6a30157a4be83b29cb195afeb640b355b097f1d94d7", size = 39946757, upload-time = "2025-11-19T22:41:08.082Z" }, + { url = "https://files.pythonhosted.org/packages/73/ab/a3965bfce6376894c76e17af095b0e360a9e1a1719e3df1e244ea6d6d893/cmake-4.1.3-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:18e1e2b7b226763017521ba8721c74d1a2a3cd7d1ec8e889b0b869d4e939370b", size = 44016695, upload-time = "2025-11-19T22:41:11.84Z" }, + { url = "https://files.pythonhosted.org/packages/a4/66/fa0e8d3c66459a616f0baf9d22933e14137c259f4b62f0dad9c3723cf42d/cmake-4.1.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6966746b25d1e9c8d32c731452e220e84331b5133544f710b21bd228a93812ca", size = 43357408, upload-time = "2025-11-19T22:41:15.302Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8f/5c43c6465af62bb16159de113438365c789c5a69261dad36746aa1ec74b8/cmake-4.1.3-py3-none-win32.whl", hash = "sha256:b1c890af27bb548d0a2c0e1affc81ad180fc17d8dfa9545e0658153446fe7db4", size = 34268275, upload-time = "2025-11-19T22:41:18.733Z" }, + { url = "https://files.pythonhosted.org/packages/c1/51/2bc56a4d8d9c2680913f1a7e0b7a33e48100f336df91176b74dda6dff8b3/cmake-4.1.3-py3-none-win_amd64.whl", hash = "sha256:fd5a2ea9a38c6109036d8c912a7db4df2de241cfbc00b7424ae246494387da80", size = 37545974, upload-time = "2025-11-19T22:41:21.85Z" }, + { url = "https://files.pythonhosted.org/packages/36/a5/ec213d5c228ab7a205abeb51cc23aa1be9b586041c40cdccc157c325822a/cmake-4.1.3-py3-none-win_arm64.whl", hash = "sha256:79bd8f92a3385cc6641949b0274cd10ee9a4f45a2c13840121b68b2e90b5af3a", size = 36337597, upload-time = "2025-11-19T22:41:24.968Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "50.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" }, + { url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" }, + { url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" }, + { url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" }, + { url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" }, + { url = "https://files.pythonhosted.org/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" }, + { url = "https://files.pythonhosted.org/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" }, + { url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" }, +] + +[[package]] +name = "datasets" +version = "4.8.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/34/14cd8e76f907f7d4dca2334cfeec9f81d30fd15c25a015f99aaea694eaed/datasets-4.8.5.tar.gz", hash = "sha256:0f0c1c3d56ffff2c93b2f4c63c95bac94f3d7e8621aea2a2a576275233bba772", size = 605649, upload-time = "2026-04-27T15:43:57.384Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/99/00f3196036501b53032c4b1ab8337a0b978dee832ed276dae3815df4e8b5/datasets-4.8.5-py3-none-any.whl", hash = "sha256:5079900781719c0e063a8efdd2cd95a31ad0c63209178669cd23cf1b926149ff", size = 528973, upload-time = "2026-04-27T15:43:53.702Z" }, +] + +[[package]] +name = "deepdiff" +version = "8.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "orderly-set" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/50/767448e792d41bfb6094ee317a355c1cb221dca24b2e178e2203bbea2a77/deepdiff-8.6.2.tar.gz", hash = "sha256:186dcbd181e4d76cef11ab05f802d0056c5d6083c5a6748c1473e9d7481e183e", size = 634860, upload-time = "2026-03-18T17:16:33.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/5f/c52bd1255db763d0cdcb7084d2e90c42119cb229302c56bdf1d0aa78abd2/deepdiff-8.6.2-py3-none-any.whl", hash = "sha256:4d22034a866c3928303a9332c279362f714192d9305bac17c498720d095fd1b4", size = 91979, upload-time = "2026-03-18T17:16:32.171Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "draccus" +version = "0.11.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "pyyaml" }, + { name = "toml" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/b0/cc399719e0cf6fea451f155798e19ea8c5e9c838b701d1565f29ea626c18/draccus-0.11.6.tar.gz", hash = "sha256:d134f576a1f4febd93c6b200df7f92e5febffe9efdc0a5f381bf50c2e0568a39", size = 67940, upload-time = "2026-06-12T13:21:19.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/f1/d56bef4563d1cfaf55dd58de298a456587b9fe0a85dabb55768d44ace8f7/draccus-0.11.6-py3-none-any.whl", hash = "sha256:1cf3f37c64766e0f17d757099b388e762f1d22c772cc2376b4e366b515fb5737", size = 85449, upload-time = "2026-06-12T13:21:18.83Z" }, +] + +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, +] + +[[package]] +name = "embodirun" +version = "0.1.0" +source = { editable = "../../" } + +[package.metadata] +requires-dist = [ + { name = "sglang", extras = ["diffusion"], marker = "python_full_version >= '3.12' and sys_platform == 'linux' and extra == 'sglang'", specifier = "==0.5.18" }, + { name = "wireless-comm", marker = "extra == 'wireless'", git = "https://github.com/BUAA-CI-LAB/WirelessComm?rev=v0.1.0" }, +] +provides-extras = ["sglang", "wireless"] + +[package.metadata.requires-dev] +dev = [ + { name = "numpy", specifier = ">=1.26,<3" }, + { name = "paramiko", specifier = ">=3.4,<5" }, + { name = "pillow", specifier = ">=10,<13" }, + { name = "pytest", specifier = ">=8,<9" }, + { name = "pyyaml", specifier = ">=6,<7" }, + { name = "rich", specifier = ">=13,<15" }, + { name = "ruff", specifier = ">=0.10" }, +] +host = [ + { name = "paramiko", specifier = ">=3.4,<5" }, + { name = "pyyaml", specifier = ">=6,<7" }, + { name = "rich", specifier = ">=13,<15" }, +] +robot-arx5 = [ + { name = "opencv-python-headless", specifier = ">=4.10,<5" }, + { name = "pillow", specifier = ">=10,<13" }, + { name = "pyrealsense2", specifier = ">=2.54,<3" }, +] +robot-fr3 = [{ name = "franky-control", specifier = ">=1.1.4,<2" }] +robot-go2 = [] +robot-so101 = [ + { name = "feetech-servo-sdk", marker = "python_full_version >= '3.12'", specifier = ">=1,<2" }, + { name = "opencv-python-headless", marker = "python_full_version >= '3.12'", specifier = ">=4.10,<5" }, +] +robot-xlerobot-external-owner = [] +sim-habitat = [ + { name = "habitat-sim", marker = "python_full_version == '3.11.*'", git = "https://github.com/facebookresearch/habitat-sim.git?rev=acbe6f4922e68145e401e55c30f9dfea460a3f24" }, + { name = "opencv-python-headless", marker = "python_full_version == '3.11.*'", specifier = ">=4.10,<5" }, + { name = "pillow", marker = "python_full_version == '3.11.*'", specifier = ">=10,<13" }, +] +sim-isaac = [ + { name = "isaacsim", extras = ["all", "extscache"], marker = "python_full_version == '3.12.*'", specifier = "==6.0.1.0" }, + { name = "opencv-python-headless", marker = "python_full_version == '3.12.*'", specifier = ">=4.10,<5" }, + { name = "pillow", marker = "python_full_version == '3.12.*'", specifier = ">=10,<13" }, +] +sim-libero = [ + { name = "lerobot", extras = ["libero"], marker = "python_full_version >= '3.12'", specifier = ">=0.6,<0.7" }, + { name = "pillow", marker = "python_full_version >= '3.12'", specifier = ">=10,<13" }, +] +sim-vlabench = [ + { name = "lerobot", marker = "python_full_version >= '3.12'", specifier = ">=0.6,<0.7" }, + { name = "pillow", marker = "python_full_version >= '3.12'", specifier = ">=10,<13" }, + { name = "vlabench", marker = "python_full_version >= '3.12'", git = "https://github.com/OpenMOSS/VLABench.git?rev=cf588fe60c0c7282174fe979f5913170cfe69017" }, +] + +[[package]] +name = "embodirun-xlerobot-owner" +version = "0.1.0" +source = { editable = "../../integrations/xlerobot_owner" } +dependencies = [ + { name = "aiohttp" }, + { name = "embodirun" }, +] + +[package.optional-dependencies] +camera = [ + { name = "opencv-python-headless" }, +] +hardware = [ + { name = "lerobot", extra = ["feetech"] }, +] +recording = [ + { name = "av" }, + { name = "datasets" }, + { name = "jsonlines" }, + { name = "lerobot" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pyarrow" }, +] +web = [ + { name = "cryptography" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.10,<4" }, + { name = "aiortc", marker = "extra == 'video'", specifier = ">=1.14,<2" }, + { name = "av", marker = "extra == 'recording'", specifier = ">=15,<16" }, + { name = "cryptography", marker = "extra == 'web'", specifier = ">=43" }, + { name = "datasets", marker = "extra == 'recording'", specifier = ">=4.8,<5" }, + { name = "embodirun", specifier = ">=0.1.0" }, + { name = "hidapi", marker = "extra == 'hid'", specifier = ">=0.14,<1" }, + { name = "jsonlines", marker = "extra == 'recording'", specifier = ">=4,<5" }, + { name = "lerobot", marker = "extra == 'recording'", specifier = ">=0.6,<0.7" }, + { name = "lerobot", extras = ["feetech"], marker = "extra == 'hardware'", specifier = ">=0.6,<0.7" }, + { name = "numpy", marker = "extra == 'recording'", specifier = ">=2,<2.3" }, + { name = "numpy", marker = "extra == 'test'", specifier = ">=2,<2.3" }, + { name = "opencv-python-headless", marker = "extra == 'camera'", specifier = ">=4.10,<5" }, + { name = "pandas", marker = "extra == 'recording'", specifier = ">=2,<3" }, + { name = "pillow", marker = "extra == 'recording'", specifier = ">=10,<13" }, + { name = "pillow", marker = "extra == 'test'", specifier = ">=10,<13" }, + { name = "pyarrow", marker = "extra == 'recording'", specifier = ">=21,<30" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=8,<9" }, + { name = "pytest-asyncio", marker = "extra == 'test'", specifier = ">=0.23,<1" }, +] +provides-extras = ["test", "web", "hardware", "hid", "camera", "recording", "video"] + +[[package]] +name = "embodirun-xlerobot-snack-recipe" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "embodirun" }, + { name = "paramiko" }, + { name = "pyyaml" }, + { name = "rich" }, +] + +[package.optional-dependencies] +hardware = [ + { name = "embodirun-xlerobot-owner", extra = ["camera", "hardware", "recording", "web"] }, + { name = "torch", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torch", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux'" }, + { name = "torchvision", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torchvision", version = "0.26.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux'" }, +] + +[package.metadata] +requires-dist = [ + { name = "embodirun", editable = "../../" }, + { name = "embodirun-xlerobot-owner", extras = ["hardware", "camera", "recording", "web"], marker = "extra == 'hardware'", editable = "../../integrations/xlerobot_owner" }, + { name = "paramiko", specifier = ">=3.4,<5" }, + { name = "pyyaml", specifier = ">=6,<7" }, + { name = "rich", specifier = ">=13,<15" }, + { name = "torch", marker = "sys_platform == 'linux' and extra == 'hardware'", index = "https://download.pytorch.org/whl/cpu" }, + { name = "torch", marker = "sys_platform != 'linux' and extra == 'hardware'" }, + { name = "torchvision", marker = "sys_platform == 'linux' and extra == 'hardware'", index = "https://download.pytorch.org/whl/cpu" }, + { name = "torchvision", marker = "sys_platform != 'linux' and extra == 'hardware'" }, +] +provides-extras = ["hardware"] + +[[package]] +name = "farama-notifications" +version = "0.0.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/91/14397890dde30adc4bee6462158933806207bc5dd10d7b4d09d5c33845cf/farama_notifications-0.0.6.tar.gz", hash = "sha256:b19acac4bb41d76e59e03394b5dd165f4761c86fa327f56307a35cbee3b60158", size = 2517, upload-time = "2026-04-24T08:43:57.603Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/f0/21f81892e4ed10f4ec3ef2e7cf8635fb76e7c0907c55d0da66be50094760/farama_notifications-0.0.6-py3-none-any.whl", hash = "sha256:f84839188efa1ce5bb361c2a84881b2dc2c0d0d7fb661ff00421820170930935", size = 2897, upload-time = "2026-04-24T08:43:56.785Z" }, +] + +[[package]] +name = "feetech-servo-sdk" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyserial" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/8e/c53d6f9a8bf3a86a635b58eeb675723f1b040f1665a0681467756c8989aa/feetech-servo-sdk-1.0.0.tar.gz", hash = "sha256:d4d3832e4b1b22a8222133a414db9f868224c2fb639426a1b11d96ddfe84e69c", size = 8392, upload-time = "2022-11-08T03:44:45.269Z" } + +[[package]] +name = "filelock" +version = "4.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/f5/14097cca69f53794270d8c7970b48321636302affe3154c7e0ba114eeff9/filelock-4.0.7.tar.gz", hash = "sha256:da5915714a70b55d167fdc7e251ad91302b0a36816fb574dfafae8f4f2c9bb21", size = 248489, upload-time = "2026-09-29T23:04:05.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/cf/1731c0dd182ee35f80a39e63afb3d03056ea0dd561b7498932b10b2e34d9/filelock-4.0.7-py3-none-any.whl", hash = "sha256:a93c4d93269b339a6af4848342c7e940d0f9928ad95eff64764699e5f1bf8a6a", size = 109242, upload-time = "2026-09-29T23:04:04.696Z" }, +] + +[[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/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/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.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "gymnasium" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "farama-notifications" }, + { name = "numpy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/ff/14b6880d703dfaca204490979d3254ccd280c99550798993319902873658/gymnasium-1.3.0.tar.gz", hash = "sha256:6939e86e835d6b71b6ba6bfd360487420876deafc79bfb7bacba83a7c446bcf3", size = 830646, upload-time = "2026-04-22T13:47:14.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/73/fda6a25f3beeb5e49d74330b44092b9e5a547395ccd478d1103ddcbff1fc/gymnasium-1.3.0-py3-none-any.whl", hash = "sha256:6b8c159a8540dcbcb221722d7efda24d78ebbcbc3bd2ea1c2611aa2a34471fc2", size = 953904, upload-time = "2026-04-22T13:47:12.13Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527, upload-time = "2026-08-03T22:33:13.243Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729, upload-time = "2026-08-03T22:33:00.721Z" }, + { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287, upload-time = "2026-08-03T22:33:02.251Z" }, + { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663, upload-time = "2026-08-03T22:33:03.802Z" }, + { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538, upload-time = "2026-08-03T22:33:05.287Z" }, + { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520, upload-time = "2026-08-03T22:33:06.81Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937, upload-time = "2026-08-03T22:33:08.616Z" }, + { url = "https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b", size = 4033128, upload-time = "2026-08-03T22:33:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3", size = 3859359, upload-time = "2026-08-03T22:33:11.725Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.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 = "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 = "invoke" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/227c48c5fe47fa178ccf1fda8f047d16c97ba926567b661e9ce2045c600c/invoke-3.0.3.tar.gz", hash = "sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c", size = 343419, upload-time = "2026-04-07T15:17:48.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/de/bbc12563bbf979618d17625a4e753ff7a078523e28d870d3626daa97261a/invoke-3.0.3-py3-none-any.whl", hash = "sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053", size = 160958, upload-time = "2026-04-07T15:17:46.875Z" }, +] + +[[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 = "jsonlines" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/87/bcda8e46c88d0e34cad2f09ee2d0c7f5957bccdb9791b0b934ec84d84be4/jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74", size = 11359, upload-time = "2023-09-01T12:34:44.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55", size = 8701, upload-time = "2023-09-01T12:34:42.563Z" }, +] + +[[package]] +name = "lerobot" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmake" }, + { name = "draccus" }, + { name = "einops" }, + { name = "gymnasium" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "setuptools" }, + { name = "termcolor" }, + { name = "torch", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torch", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux'" }, + { name = "torchvision", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" }, + { name = "torchvision", version = "0.26.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux'" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/98/797e5685dd1dc6b932fa6b3c477b0ec37e4e952e36124152df81be02cce5/lerobot-0.6.1.tar.gz", hash = "sha256:2111d67d72d9f6c2379e3b8d3ea41b58855066314d88f9c0893579b7707f04b5", size = 1348864, upload-time = "2026-08-03T14:19:56.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c0/8b17580cfae9d970edc1b9138a2231fb5416eb2e8e03e3c79f4355f978c2/lerobot-0.6.1-py3-none-any.whl", hash = "sha256:1894516040c65f80a45bd9741f8174aae90ed5d93da0627ab4f1a85fd8d75e90", size = 1728105, upload-time = "2026-08-03T14:19:54.439Z" }, +] + +[package.optional-dependencies] +feetech = [ + { name = "deepdiff" }, + { name = "feetech-servo-sdk" }, + { name = "pyserial" }, +] + +[[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.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, +] + +[[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 = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "multidict" +version = "6.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/99/1d4d69c3512d0ddbfa3a1b69cfd9a151012ab2eb4eabbb096201b1f0b7d8/multidict-6.9.1.tar.gz", hash = "sha256:0f06e60fa190aa7abd0914c2a766736fdc8e9f34878c4346338534b73d1b20e2", size = 182404, upload-time = "2026-09-21T17:59:05.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/0d/4b5afb6d3e545c9af0cdfe2db8f6f4c6664568c23d863d888674e447e6a4/multidict-6.9.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:29138fef49828542e859828107e42e50d0e587c513b7eb4b2d92bade2b0860fe", size = 98360, upload-time = "2026-09-21T17:55:11.508Z" }, + { url = "https://files.pythonhosted.org/packages/89/ab/1b9ca66251899981b21138b87da9d5a9c2c81af12b1ea7d19466972f7fe2/multidict-6.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:19e31815d41cefc365489e591d105d2baceb2f65aa75d29471fbdbda8651e006", size = 59979, upload-time = "2026-09-21T17:55:12.866Z" }, + { url = "https://files.pythonhosted.org/packages/36/eb/6ae44062466c26c8469ef43f2481a6a48d8cea0587b2d54514ec92e2adfd/multidict-6.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ed30be8918e18c8bed0a2e8b70639ecf02feb61ed00ca2e41cfcb2a50fa3f42", size = 57736, upload-time = "2026-09-21T17:55:14.223Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5c/a67817593019257a4ac8b0d1b4c426030e637c047b0692ef405439ecea7a/multidict-6.9.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:637f4ae36264bd7b8d9a60193acddc1d735ad52e8ed53a19931ea6d921fea8e5", size = 333097, upload-time = "2026-09-21T17:55:15.591Z" }, + { url = "https://files.pythonhosted.org/packages/90/bf/599ae2e6222822d88a247a8a7ae82fe6fd25d5700757b79603d5edafe6a0/multidict-6.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35fc236507fb1b3138f0af5ecd5f94ed752d4d6d826248eae425f86204013eea", size = 334196, upload-time = "2026-09-21T17:55:17.015Z" }, + { url = "https://files.pythonhosted.org/packages/15/10/d8aac5acacbe7f5c117866c776ec26d5f15a868759b6f37ad8e7ed3b5b02/multidict-6.9.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e58952f04772f59f11c6e007471449809a30165188669bca8fdb19dde40a8f24", size = 317465, upload-time = "2026-09-21T17:55:18.412Z" }, + { url = "https://files.pythonhosted.org/packages/19/0a/714f796f7293a8b1c5c3f465a26996231d5450c54e85d64ef1258c091134/multidict-6.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d35a4f1c63f07fbb8c8f9946dea98b21eddf6c57421585f71d91864be3ba2a24", size = 345914, upload-time = "2026-09-21T17:55:20.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b1/e37fbf769c567be277bcf32df6234035a4384677fcc1bd852752be3d6b93/multidict-6.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b5f8771aaaed7f80e84a4e471d2f29ab6721e4595075e54d03ae1ed951b2000a", size = 351163, upload-time = "2026-09-21T17:55:22.21Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5b/db08419c1e1f7c9d60cfd2787b2b517d7ae4ebbda8281b48a33eb5141467/multidict-6.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:976fd7689d69ec78d67d31d38d396d8adb562f7e8368279f76aed4aa451fa06d", size = 336881, upload-time = "2026-09-21T17:55:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/9b/07/cc9bc8a62651d2d53ab93ee4993b3a71b7cb78eb8ebfc9c757a5b6698617/multidict-6.9.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:95052e8777a86bae87c0bd0b5ab22d809e3d1d02bf69e3e66ddda5ba75a05805", size = 303405, upload-time = "2026-09-21T17:55:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0c/8e912afafa70e944dbb8bec4b66ca6e008511395278c0d3dd0e89567536a/multidict-6.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1a8adfcaf96f587ab138476eaddef95f29b8a2a8a9afbfea8d2fd62180995d02", size = 324265, upload-time = "2026-09-21T17:55:26.989Z" }, + { url = "https://files.pythonhosted.org/packages/66/6a/62c2af80fb085e6234805017af857b8e913dfac7a55e9c1349c27768c58a/multidict-6.9.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1a53de2772cfb74559df2eb4456ec4eeb908435ec55a84b69370d9d745d62aa8", size = 322085, upload-time = "2026-09-21T17:55:28.732Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6b/35bf801b336fd960811207203ffdcdc24acc3558b3e7ff2e1c914b141e70/multidict-6.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f2ce963299d42fa3f22a90adc0fdf174792ffef5ff4c7ffb68260548fb05580", size = 338107, upload-time = "2026-09-21T17:55:30.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/41/495ef65bf5bba29d142d81b3fe1b8154b919bed70e96491b1e93c3c26f0f/multidict-6.9.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5b30ddf7234e611ca877575b62840e6af5977f92f1f9d532eedbb05a44ff8004", size = 339631, upload-time = "2026-09-21T17:55:32.012Z" }, + { url = "https://files.pythonhosted.org/packages/19/bd/057fdff5f4e04dcd40a960e38f77d19d3c4b67dd243ffa5f43718edb29fc/multidict-6.9.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63ada7ee2e9345695f9e9bc4c65d72222253f07b1ac94fd0e37555cc6f3c7f60", size = 302080, upload-time = "2026-09-21T17:55:33.817Z" }, + { url = "https://files.pythonhosted.org/packages/3f/de/9ace933ee8dad808632523726f42255b09600087219e3d4ead7369820910/multidict-6.9.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c95601ed98fad3f6e2f8fe809c3b526b0fab31ef525e00a155e227f3d17f58a", size = 341107, upload-time = "2026-09-21T17:55:35.471Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ac/7c1204406097bfc5c283d4a3287d61166807d189917567df4c1318484fc4/multidict-6.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c148e8b596000dd3e4bfe206e70f3e666be18d72032e0012555f2373c52e35d6", size = 333482, upload-time = "2026-09-21T17:55:37.002Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/a70c32ea3299ebe00f44533740cb46905717c51faf29bbd3ce8bb5886d9d/multidict-6.9.1-cp312-cp312-win32.whl", hash = "sha256:f9dad513626a33670f17cddc6078e30e311f444c957e8dbfc5b2b4603c8b4edb", size = 52548, upload-time = "2026-09-21T17:55:38.529Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2e/8c9c2591df01e5692ad1bf92febb082a17b1c6c3a19aa8b7ff49988fd989/multidict-6.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:a16a1dc8529f9e734a41c3b856f3eae7ebacdc061dde3f8a844e0c7889c97203", size = 59622, upload-time = "2026-09-21T17:55:39.86Z" }, + { url = "https://files.pythonhosted.org/packages/d0/95/59f4472ec512bc180fd207899594c7803ece96262ff9aaa3a4b6d7da6940/multidict-6.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:361f7206cf341ba94fb015688f5c8b480f8e63bd58a4c14a48aeca7851a241cc", size = 54930, upload-time = "2026-09-21T17:55:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/be/59/e26cb779be4c591d1a910f59d29aca9fba4de70349840a833beba2652371/multidict-6.9.1-py3-none-any.whl", hash = "sha256:7bf6478188f4e47bf5686e8a33da4ae28bf43b1b2528d9ee144d28492bfac60b", size = 19176, upload-time = "2026-09-21T17:59:03.501Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "networkx" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/76/3af777226b63a5e64a6b36b1ec5855c14e2b94a37096d4760e595fc43511/networkx-3.7.tar.gz", hash = "sha256:fd77a511bd90f39f3d016351345b52cf5319b813bdca01de3f755d3cca62e96a", size = 1866482, upload-time = "2026-09-21T16:45:16.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/cd/fe58041e9011f307c490e3e17dd48cc516448f7c698a3f2d9d9d65d7e6a8/networkx-3.7-py3-none-any.whl", hash = "sha256:e3fd2c13a7814cee3746340d8d7f8598a67f16a58bf47fb7f8793fab6efca1b0", size = 2142205, upload-time = "2026-09-21T16:45:14.609Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, +] + +[[package]] +name = "opencv-python-headless" +version = "4.13.0.92" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/42/2310883be3b8826ac58c3f2787b9358a2d46923d61f88fedf930bc59c60c/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1a7d040ac656c11b8c38677cc8cccdc149f98535089dbe5b081e80a4e5903209", size = 46247192, upload-time = "2026-02-05T07:01:35.187Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1e/6f9e38005a6f7f22af785df42a43139d0e20f169eb5787ce8be37ee7fcc9/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:3e0a6f0a37994ec6ce5f59e936be21d5d6384a4556f2d2da9c2f9c5dc948394c", size = 32568914, upload-time = "2026-02-05T07:01:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/21/76/9417a6aef9def70e467a5bf560579f816148a4c658b7d525581b356eda9e/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c8cfc8e87ed452b5cecb9419473ee5560a989859fe1d10d1ce11ae87b09a2cb", size = 33703709, upload-time = "2026-02-05T10:24:46.469Z" }, + { url = "https://files.pythonhosted.org/packages/92/ce/bd17ff5772938267fd49716e94ca24f616ff4cb1ff4c6be13085108037be/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0525a3d2c0b46c611e2130b5fdebc94cf404845d8fa64d2f3a3b679572a5bd22", size = 56016764, upload-time = "2026-02-05T10:26:48.904Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b4/b7bcbf7c874665825a8c8e1097e93ea25d1f1d210a3e20d4451d01da30aa/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb60e36b237b1ebd40a912da5384b348df8ed534f6f644d8e0b4f103e272ba7d", size = 35010236, upload-time = "2026-02-05T10:28:11.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/33/b5db29a6c00eb8f50708110d8d453747ca125c8b805bc437b289dbdcc057/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0bd48544f77c68b2941392fcdf9bcd2b9cdf00e98cb8c29b2455d194763cf99e", size = 60391106, upload-time = "2026-02-05T10:30:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c3/52cfea47cd33e53e8c0fbd6e7c800b457245c1fda7d61660b4ffe9596a7f/opencv_python_headless-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:a7cf08e5b191f4ebb530791acc0825a7986e0d0dee2a3c491184bd8599848a4b", size = 30812232, upload-time = "2026-02-05T07:02:29.594Z" }, + { url = "https://files.pythonhosted.org/packages/4a/90/b338326131ccb2aaa3c2c85d00f41822c0050139a4bfe723cfd95455bd2d/opencv_python_headless-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:77a82fe35ddcec0f62c15f2ba8a12ecc2ed4207c17b0902c7a3151ae29f37fb6", size = 40070414, upload-time = "2026-02-05T07:02:26.448Z" }, +] + +[[package]] +name = "orderly-set" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/88/39c83c35d5e97cc203e9e77a4f93bf87ec89cf6a22ac4818fdcc65d66584/orderly_set-5.5.0.tar.gz", hash = "sha256:e87185c8e4d8afa64e7f8160ee2c542a475b738bc891dc3f58102e654125e6ce", size = 27414, upload-time = "2025-07-10T20:10:55.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl", hash = "sha256:46f0b801948e98f427b412fcabb831677194c05c3b699b80de260374baa0b1e7", size = 13068, upload-time = "2025-07-10T20:10:54.377Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, +] + +[[package]] +name = "paramiko" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bcrypt" }, + { name = "cryptography" }, + { name = "invoke" }, + { name = "pynacl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/e7/81fdcbc7f190cdb058cffc9431587eb289833bdd633e2002455ca9bb13d4/paramiko-4.0.0.tar.gz", hash = "sha256:6a25f07b380cc9c9a88d2b920ad37167ac4667f8d9886ccebd8f90f654b5d69f", size = 1630743, upload-time = "2025-08-04T01:02:03.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl", hash = "sha256:0e20e00ac666503bf0b4eda3b6d833465a2b7aff2e2b3d79a8bba5ef144ee3b9", size = 223932, upload-time = "2025-08-04T01:02:02.029Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, +] + +[[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/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/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 = "pyarrow" +version = "25.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/e3/27f57f80141379d60defe6703eb50a707325706f07fedfd1312c7a751995/pyarrow-25.0.1.tar.gz", hash = "sha256:9150a83248bfed9813ea3c3af74c3856c1984d444aa28e58bf7733b9750ddf6a", size = 1201653, upload-time = "2026-08-10T12:40:53.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/e2/9ab15b88cbfac28e16419ce5439ec29234c5172cb8259301b4ba639bdec0/pyarrow-25.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:df961f2e7ae9cf496459259d798652c70625f6c080650d6952f8c04053c58ee9", size = 35861559, upload-time = "2026-08-10T12:38:02.567Z" }, + { url = "https://files.pythonhosted.org/packages/58/79/a0036dbe1eabe1f73127427342f1d99982584c4a2cde2651d6c93499c6f6/pyarrow-25.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:cc4aa407fde9fc660be3939e49ea31f50f3e9fec17c0ec63159f7711edd3efc9", size = 37628383, upload-time = "2026-08-10T12:38:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/13/49/d93a57d375f4bf0cf82913dd6bb54acafde83dd993be2282c81ac5616cad/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4340f0ba6c1d2e13f21658de1d7c662ca2545018568d0030a1e9afca159d87e3", size = 46820190, upload-time = "2026-08-10T12:38:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/60/c9/711ca85d79f1ec98f29a5eae2b051e25b4ecec5de3e3c0e2d5c5dcb15664/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5389cdf79447ed1515c9e31620e6e1e2302249564d603f2ad727d4f6d313e4c3", size = 50102437, upload-time = "2026-08-10T12:38:22.487Z" }, + { url = "https://files.pythonhosted.org/packages/80/53/8fb8359ff17cfb6263a1cf3ebf7caec9fe197de118719e84fcb1d0618026/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d51592cb7561e87877c506113e7adbf1342ab579e6c21f0ef44b8ba41cb74c80", size = 49942424, upload-time = "2026-08-10T12:38:28.755Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/4e5ae02a9341571b18a6fca380ac7a58ce6ddae7ab3c060208c0a1e79f02/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6109c94d8b9f3b17a041daca16cacb2f651ad8f1ef70a4232c2c0f37a23da2a8", size = 53144206, upload-time = "2026-08-10T12:38:34.862Z" }, + { url = "https://files.pythonhosted.org/packages/65/ee/197cbf47e49f83e6ebeb946a5259a48a638dea27ac774db42fe78022179d/pyarrow-25.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8858d7bfc22e3f51529aeaa4077225029724623e4595dc9eff8c793935c34140", size = 27953934, upload-time = "2026-08-10T12:38:39.808Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +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 = "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 = "pynacl" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, + { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, +] + +[[package]] +name = "pyserial" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, +] + +[[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 = "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 = "pytz" +version = "2026.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ed/fa23d28713004418bbf2407127f80f385bfb0bf4e677bef02c25c27b00ee/pytz-2026.4.tar.gz", hash = "sha256:464303645bafafd72418898368b2429458f709cf1eb6a15372fbcc396b64da63", size = 318444, upload-time = "2026-09-24T03:58:40.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/11/e3df59614bebe0ca50c905442603eda64ae7ac19811d4e4f74aa2d51ca85/pytz-2026.4-py2.py3-none-any.whl", hash = "sha256:9d514388fbc89ca0833203464272ac485b8828568ab73532f5020f17e892a0ff", size = 506747, upload-time = "2026-09-24T03:58:38.629Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, +] + +[[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 = "14.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "setuptools" +version = "81.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, +] + +[[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 = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "termcolor" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, +] + +[[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 = "torch" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "sys_platform != 'linux'", +] +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, +] + +[[package]] +name = "torch" +version = "2.11.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "sys_platform == 'linux'", +] +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:2db3ae5404e32cb42b5fcbd94f13607761eaec0cf1687fde95095289d1e26cfb", upload-time = "2026-04-28T00:06:06Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:70ecb2659af6373b7c5336e692e665605b0201ea21ff51aaea47e1d75ea6b5aa", upload-time = "2026-04-28T00:06:14Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f82e2ae20c1545bb03997d1cc3143d94e14b800038669ee1aca45808a9acc338", upload-time = "2026-04-28T00:06:24Z" }, +] + +[[package]] +name = "torchvision" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "sys_platform != 'linux'", +] +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch", version = "2.11.0", source = { registry = "https://pypi.org/simple" } }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502, upload-time = "2026-03-23T18:12:57.326Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155, upload-time = "2026-03-23T18:12:32.652Z" }, +] + +[[package]] +name = "torchvision" +version = "0.26.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "sys_platform == 'linux'", +] +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" } }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:17f0b542331fc94230b4214c6d123f038af7330fd81019608c0d2402f3bc3079", upload-time = "2026-03-23T15:36:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:cf547dc0975eb40bc3249be4ccbeb736597d2c3ece305b1c4e5b7a5dd7363567", upload-time = "2026-03-23T15:36:09Z" }, +] + +[[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 = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/31/3d74fa778a63b98b7374323befcc0be5ab3bd94afd4096a0124e7379152c/tzdata-2026.4.tar.gz", hash = "sha256:f1b8bd365d8d210c55353f4d7f8d6d8561c0ba50d704b700d195a9424bba0d79", size = 199350, upload-time = "2026-09-12T12:56:03.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl", hash = "sha256:c2169a8b0a7a5e9674da5a135ccdfb2b3e671b333ed9fed17b41f73c34476e81", size = 347494, upload-time = "2026-09-12T12:56:01.67Z" }, +] + +[[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 = "xxhash" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/a5/1386f35da1475fcaeef42581deae73417c6d2a6a0b2d2e8914de18844dcd/xxhash-4.0.1.tar.gz", hash = "sha256:d55bf4ef10eb09b8b6866790e083d26d087d84caa3cc0946ba87c3ca7ecaf7b7", size = 101513, upload-time = "2026-08-17T08:24:08.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/6c/dc7cffeadd06336cd934947187cd38abb263103bbc552ca0f55fe4ff595a/xxhash-4.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1ee523f51718e41753f04f7102bb4dc55a18d2ea5cbaceef8ec7ca08571bd428", size = 38444, upload-time = "2026-08-17T08:21:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/75/c9/cf736f6db8c3273af18925061572db0d4357818a9ce425f4b5fb0021918e/xxhash-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:515a822c73abbf6a0b7c70976d9662be342835c9d78b8dc7c023411f39c35dbc", size = 36195, upload-time = "2026-08-17T08:35:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/da/a2/ca1929354b6851529d0148f7f335b5e2b0281f83bab3e19f0896dc579796/xxhash-4.0.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f5d031f35962e5483a613214e61f09fe24ab523062c3646d592dc16c4a217451", size = 253113, upload-time = "2026-08-17T08:20:52.152Z" }, + { url = "https://files.pythonhosted.org/packages/de/bb/542005206af59518bc8d78a210f1e0172217bc53beb32f64a5b632e72b6b/xxhash-4.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0264844a09b538c894e5eff25313d941deb4dedec2131b98418a71a3c9944e", size = 276525, upload-time = "2026-08-17T08:21:01.886Z" }, + { url = "https://files.pythonhosted.org/packages/1b/df/607cff25dcb0f1d35c3b04493f6ad8471edb03fd4eacbdcc5ceddef1f3e9/xxhash-4.0.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1642907941ee4b75aacc3db688af52ea02ca2305ab22af7ee686ed726b332684", size = 297703, upload-time = "2026-08-17T08:21:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/15/ba/9d2275eea0b9d9c6b02921be23f7588356c60df95c763b25f0e045894d43/xxhash-4.0.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4af350bc3f329970c0e3a59af84a8a30998bf8a9167eb50cd48e59baaa1d7bec", size = 280252, upload-time = "2026-08-17T08:20:47.299Z" }, + { url = "https://files.pythonhosted.org/packages/1d/aa/2299d9f6369e550aef2abb64945e39daa34412725aa46a20d99b74d76f67/xxhash-4.0.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ba782ca3bf1e81492611152b9a0d5264971339e95e34d69de0ac2c926be496d", size = 511041, upload-time = "2026-08-17T08:20:36.771Z" }, + { url = "https://files.pythonhosted.org/packages/83/97/31bd8b8279e6935a0719f6910ced15e9d5a2cd554b253f6027ce1b5a1c2c/xxhash-4.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:237b8f63a2a0fcfb1ffc06e21dad23add44e6d354b2b014364a1d41e419a4dee", size = 261812, upload-time = "2026-08-17T08:22:00.469Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c1/d180a2da23c105d8e0b02d54f9f5841013fc81c233010ec781e31f1aee4c/xxhash-4.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:81507a68ba84c55241fb61cce1469f473a5da4205fc8ef6f698e5948eea8dd88", size = 339878, upload-time = "2026-08-17T08:35:17.626Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3d/f584cd3172fe934f0f5a0a3917d0d7ce781f74d794fd43bb72be71c3ef6f/xxhash-4.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1ea31d61bcd2cd2f3ec4ca80a64187bbd7948f490b63cf0dcbc6e717b4c1e9", size = 272871, upload-time = "2026-08-17T08:20:56.067Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/2c7956b2b551682e00b9aebce9ceb0a991a131d65f9850c09f5f9760be2e/xxhash-4.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:06713a5aaf1d0905c5579416c020c02e42b3ceb931e86c7d3b7fb85403dee3f3", size = 301440, upload-time = "2026-08-17T08:21:35.911Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/0739f6482184a8026f4b022718f5f815d352059312e80696825433f0a8e7/xxhash-4.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8cda075b10bb3917b002c74a04f9e02b7d13b5bf732571404d51c52b11c7329", size = 260157, upload-time = "2026-08-17T08:22:01.416Z" }, + { url = "https://files.pythonhosted.org/packages/a1/25/b31a7bcf1d7d116842812e54f9b944843b4236ea4fa85634e8259f342212/xxhash-4.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c10b9206753b64aa791b35b201485477525b26fdec5bf86e8364c388a03e2592", size = 278233, upload-time = "2026-08-17T08:21:15.674Z" }, + { url = "https://files.pythonhosted.org/packages/db/e8/5293bae090fc6119dbc5fcf5c4cc0e1536394b52d73b7904d033836c73db/xxhash-4.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f3e1a44af01b6692de0ec6caba5f0bf93ceb36896e02b7fc00952c6ea7ef39e1", size = 330270, upload-time = "2026-08-17T08:20:51.128Z" }, + { url = "https://files.pythonhosted.org/packages/72/9e/e2ab12d40921f3f34c9317637d65e011aeababf8288356ea8d527de2c1d0/xxhash-4.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6fc415b5568bd9accc7187f1729a99707330c0a67a8b9f93c1149ed573ed75d", size = 478555, upload-time = "2026-08-17T08:22:04.183Z" }, + { url = "https://files.pythonhosted.org/packages/6d/32/c6148d39a49efa95f39b4cf0d41ef35a487f3b30f6fb1fc8fe8d8eab577e/xxhash-4.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:96d8de55029d42251945531f6aa7590c32b48163c66a43bf29d8657d7446a377", size = 258174, upload-time = "2026-08-17T08:35:21.18Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fb/0b04b68d6c5bc71c7a2c344f1287327b67e607f28fbcfd937697caca64b6/xxhash-4.0.1-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:0163b5d259de23ae9e07b7eabf435ce4704f6f205589a2b154e6af4be985ce1b", size = 20767, upload-time = "2026-08-17T08:21:00.806Z" }, + { url = "https://files.pythonhosted.org/packages/a6/be/476092aba34d1fcd313e1613a3bb3bc692f253d167b54bc90049043b5034/xxhash-4.0.1-cp312-cp312-win32.whl", hash = "sha256:1216f7ba5683f17a89eb7dcb4bc50a0b743dfe1902278d7b3d0786f538118433", size = 34669, upload-time = "2026-08-17T08:21:49.486Z" }, + { url = "https://files.pythonhosted.org/packages/aa/02/f9413d94fae43cec6d1a74c4f12156c6f4a7f5fd50e1d34defebdee3dec9/xxhash-4.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c2d525a3afabcd8e3549d85fc7e111fde6bc302d06a1893fe73adb79823415e", size = 37073, upload-time = "2026-08-17T08:22:04.886Z" }, + { url = "https://files.pythonhosted.org/packages/c1/83/6fe93c1b95acf962bc61a246df09dc2dcce895ccfc1080c9f48d0b652b92/xxhash-4.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:86b2b12bec60c678ed8f5cca0258ad93a8928ebddb6ca7732f0875afe1451d1a", size = 33299, upload-time = "2026-08-17T08:35:12.708Z" }, + { url = "https://files.pythonhosted.org/packages/86/79/9127ff42a887a348dc4ce3211cf1a962836887adee6f57078132bfba78b4/xxhash-4.0.1-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:ff48915bf1871a1f19f74c11834c6329443d306cedc0c05fe7fe617810422a80", size = 31836, upload-time = "2026-08-17T08:36:28.261Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/f238693bfdd642adb59c99683964d46d9947fe721ff44d3bd850ae675407/xxhash-4.0.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a76345f5aceb4ec404918edf9c7f2b5507db864dc0d7455982009ac0890b57b", size = 34453, upload-time = "2026-08-17T08:23:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/40/4b/796ace33cdfb75c91ba6d11615c3bd436355b9f3103e05865bbee9abce57/xxhash-4.0.1-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31d86f9e81f3e84e00131ac7c54caf5119ae4ddd82c09c31cff597c813ce1ee2", size = 38488, upload-time = "2026-08-17T08:23:59.901Z" }, + { url = "https://files.pythonhosted.org/packages/ad/23/2d549e5d5d7759eaf9ac2d2d2ab81ff60f1bb2b52cdaae8e5ec5c6524354/xxhash-4.0.1-graalpy312-graalpy250_312_native-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deca2a30d983d240b8375ec2ee0a4288e72042827fc61df2f7671f8467e4cb2f", size = 38206, upload-time = "2026-08-17T08:36:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/79/98/1ee576b27f78e6107ee4ea8ac03e8a52888dff256e57d560f8282c195563/xxhash-4.0.1-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:7c343ee174d417a44d0c3355602c0cbbfa52a04d1bbbf1723378c7d2c8f60626", size = 37127, upload-time = "2026-08-17T08:23:42.705Z" }, +] + +[[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/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/54/22/318c7980066769c6bcd9221ed2248294f5698811da099013098c670565ed/yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3", size = 63617, upload-time = "2026-09-15T19:34:59.616Z" }, +] diff --git a/integrations/xlerobot_owner/pyproject.toml b/integrations/xlerobot_owner/pyproject.toml index ee1a2ce..4cc59dd 100644 --- a/integrations/xlerobot_owner/pyproject.toml +++ b/integrations/xlerobot_owner/pyproject.toml @@ -17,12 +17,24 @@ dependencies = [ # The owner tests exercise the HTTP boundary and recorder with fakes. Keep # these dependencies separate from the core deployment package so installing # EmbodiRun itself does not pull in a test runner or data-processing stack. -test = ["pytest>=8,<9", "pytest-asyncio>=0.23,<1", "numpy>=1.26,<2", "Pillow>=10,<13"] +test = ["pytest>=8,<9", "pytest-asyncio>=0.23,<1", "numpy>=2,<2.3", "Pillow>=10,<13"] web = ["cryptography>=43"] -hardware = ["lerobot>=0.6,<0.7"] +hardware = ["lerobot[feetech]>=0.6,<0.7"] hid = ["hidapi>=0.14,<1"] camera = ["opencv-python-headless>=4.10,<5"] -recording = ["lerobot>=0.6,<0.7", "numpy>=1.26,<2", "Pillow>=10,<13"] +# LeRobotDataset imports its table/image writer stack even when use_videos=False. +# This export uses images with use_videos=False, so it does not need video +# decoding or torchcodec. +recording = [ + "lerobot>=0.6,<0.7", + "numpy>=2,<2.3", + "Pillow>=10,<13", + "datasets>=4.8,<5", + "pandas>=2,<3", + "pyarrow>=21,<30", + "av>=15,<16", + "jsonlines>=4,<5", +] video = ["aiortc>=1.14,<2"] [project.scripts] diff --git a/integrations/xlerobot_owner/src/embodirun_xlerobot_owner/recording.py b/integrations/xlerobot_owner/src/embodirun_xlerobot_owner/recording.py index ca3e12f..64b47af 100644 --- a/integrations/xlerobot_owner/src/embodirun_xlerobot_owner/recording.py +++ b/integrations/xlerobot_owner/src/embodirun_xlerobot_owner/recording.py @@ -1763,8 +1763,8 @@ def export_lerobot( dataset_class = dataset_module.LeRobotDataset except Exception as exc: raise LeRobotExportError( - "LeRobot export requires the modern LeRobot package (0.4+/0.5); " - "install the project extra, for example `pip install 'lerobot>=0.4'`." + "LeRobot export requires the LeRobot dataset writer and its table/image dependencies; " + "install the XLeRobot owner's recording extra." ) from exc try: numpy_module = importlib.import_module("numpy") diff --git a/pyproject.toml b/pyproject.toml index 9f1088f..d13feca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,6 +132,7 @@ include = ["embodirun*"] [tool.pytest.ini_options] testpaths = ["tests"] +pythonpath = ["."] addopts = "-ra" [tool.ruff] diff --git a/scripts/check_recipe_software.py b/scripts/check_recipe_software.py new file mode 100644 index 0000000..b18f8e7 --- /dev/null +++ b/scripts/check_recipe_software.py @@ -0,0 +1,171 @@ +"""Exercise installed software Recipes through both public entrypoints (Python 3.12).""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import shlex +import subprocess +import sys +import tempfile +from pathlib import Path + +import tomllib + +PROJECTS = {"xlerobot": "xlerobot_snack_delivery", "microduck": "microduck_vln"} + + +def check(recipe: str, python: Path, source: Path, work: Path) -> dict: + cli = [str(python.with_name("embodirun")), "example"] + env = { + **os.environ, + "EMBODIRUN_SOURCE_ROOT": str(source), + "EMBODIRUN_SCENE_PYTHON": str(python), + "EMBODIRUN_EXAMPLE_PYTHON": str(python), + } + shell = ["bash", str(source / "examples/run.sh")] + + def run(argv: list[str], *, code: int = 0, overrides: dict | None = None, copied_hint: bool = False) -> str: + inherited = {key: value for key, value in os.environ.items() if not key.startswith("EMBODIRUN_")} + result = subprocess.run( + argv, + cwd=work, + env={**(inherited if copied_hint else env), **(overrides or {})}, + capture_output=True, + text=True, + timeout=90, + ) + if result.returncode != code: + raise RuntimeError( + f"{shlex.join(argv)} returned {result.returncode}, expected {code}\n{result.stdout}\n{result.stderr}" + ) + return result.stdout + + directory = work / "recipe with spaces" + initialized = run([*cli, "init", recipe, "--output", str(directory)]) + manifest = str(directory / "example.local.yaml") + hint = next(line.removeprefix("Next: ") for line in initialized.splitlines() if line.startswith("Next: ")) + run(shlex.split(hint), copied_hint=True) + for action in ("validate", "plan"): + for entry in (cli, shell): + run([*entry, manifest, action]) + + reports = [] + text_outputs = [] + for entry in (cli, shell): + reports.append(json.loads(run([*entry, manifest, "check", "--mode", "software", "--json"]))) + text_outputs.append(run([*entry, manifest, "check", "--mode", "software"])) + if reports[0] != reports[1] or text_outputs[0] != text_outputs[1]: + raise RuntimeError("CLI and shell software checks disagree") + report = reports[0] + if report["status"] != "passed" or report["issues"] or report["environment"]["version"][:2] != [3, 12]: + raise RuntimeError(f"Software prerequisites failed: {report}") + boundary = "CUDA/EGL" if recipe == "microduck" else "Physical device connection" + if not any(boundary in item for item in report["unverified"]): + raise RuntimeError("Software report lost its simulation/hardware boundary") + check_hint = next( + line.split(": ", 1)[1] + for line in initialized.splitlines() + if line.startswith(("Check software inputs: ", "Check rehearsal inputs: ")) + ) + if json.loads(run(shlex.split(check_hint), copied_hint=True)) != report: + raise RuntimeError("The printed software check command selects a different environment") + missing = json.loads( + run( + [*cli, manifest, "check", "--mode", "software", "--json"], + code=2, + overrides={"EMBODIRUN_SCENE_PYTHON": str(work / "missing/bin/python")}, + ) + ) + if [item["code"] for item in missing["issues"]] != ["python_missing"]: + raise RuntimeError(f"Missing environment diagnostic is incorrect: {missing}") + if "setup --mode software" not in missing["issues"][0]["next_action"]: + raise RuntimeError("Missing environment diagnostic lost its software setup hint") + default = json.loads(run([*cli, manifest, "check", "--json"], code=2)) + expected_mode, issue = ("simulation", "asset_missing") if recipe == "microduck" else ("hardware", "fixture_route") + if default["mode"] != expected_mode or not any(item["code"] == issue for item in default["issues"]): + raise RuntimeError("Default check accepted an unprepared simulation/hardware Recipe") + if (directory / "runs").exists(): + raise RuntimeError("Configuration/software checks created launcher outputs") + + checks = [ + "init/validate/plan outside the checkout with space-containing paths", + "matching CLI/shell plain and JSON software checks", + "copyable printed validate and software-check hints without inherited Recipe variables", + "missing-environment diagnostic and default simulation/hardware rejection", + "no launcher output from read-only checks", + ] + if recipe == "xlerobot": + for entry in (cli, shell): + before = set((directory / "runs").glob("*/run.json")) + run([*entry, manifest, "dry-run"]) + created = set((directory / "runs").glob("*/run.json")) - before + if len(created) != 1: + raise RuntimeError("Fixture rehearsal did not create exactly one run report") + path = created.pop() + if json.loads(path.read_text())["status"] != "complete": + raise RuntimeError("Fixture rehearsal failed") + status = json.loads((path.parent / "result/status.json").read_text()) + if ( + status["status"] != "completed" + or status["task_success"] != "unverified" + or status["physical_success"] is not None + ): + raise RuntimeError(f"Fixture rehearsal lost its physical acceptance boundary: {status}") + if list((directory / "runs").glob(".launcher-*.json")): + raise RuntimeError("Fixture rehearsal left a launcher pointer") + checks.append("both fixture rehearsals completed, retained unverified physical results and cleaned launchers") + if any(path.stat().st_uid != os.getuid() for path in directory.rglob("*")): + raise RuntimeError("Recipe files are not owned by the caller") + checks.append("all generated files are owned by the caller") + + inventory = json.loads( + run( + [ + str(python), + "-c", + "import importlib.metadata as m, json; " + "print(json.dumps({d.metadata['Name']: d.version for d in m.distributions()}))", + ] + ) + ) + packages = {re.sub(r"[-_.]+", "-", name).lower(): version for name, version in inventory.items()} + lock = tomllib.loads((source / "examples" / PROJECTS[recipe] / "uv.lock").read_text()) + locked = {package["name"]: package["version"] for package in lock["package"]} + if any(locked.get(name) != version for name, version in packages.items()): + raise RuntimeError(f"Installed inventory differs from the Recipe lock: {packages}") + if {"torch", "torchvision", "mujoco", "numpy", "pillow", "transformers", "embodirun-xlerobot-owner"}.intersection( + packages + ): + raise RuntimeError("The software profile installed model/simulation/hardware dependencies") + checks.append("installed package versions match the Recipe lock; no model/simulation/hardware dependencies") + return { + "recipe": recipe, + "status": "passed", + "python": report["environment"]["version"], + "packages": dict(sorted(packages.items())), + "checks": checks, + "unverified": report["unverified"], + } + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--recipe", choices=PROJECTS, required=True) + parser.add_argument("--python", type=Path, default=Path(sys.executable)) + parser.add_argument("--source-root", type=Path, default=Path(__file__).resolve().parent.parent) + parser.add_argument("--work-dir", type=Path, help="parent for a temporary caller-owned Recipe directory") + parser.add_argument("--report", type=Path, help="save the inventory for native/container comparison") + args = parser.parse_args() + with tempfile.TemporaryDirectory(prefix="recipe software ", dir=args.work_dir) as directory: + report = check(args.recipe, args.python.absolute(), args.source_root.resolve(), Path(directory)) + output = json.dumps(report, indent=2) + "\n" + if args.report: + args.report.write_text(output) + print(output, end="") + + +if __name__ == "__main__": + main() diff --git a/src/embodirun/services/host/cli/cli.py b/src/embodirun/services/host/cli/cli.py index e193d7d..34bec32 100644 --- a/src/embodirun/services/host/cli/cli.py +++ b/src/embodirun/services/host/cli/cli.py @@ -12,20 +12,21 @@ from embodirun.deployment.executor import Executor, executor_for from embodirun.deployment.plan import ServiceError, build_plan -from .command import control, down, init, probe, run, sync, up, validate +from .command import control, down, example, init, probe, run, sync, up, validate from .context import CommandContext, state_path from .progress import ConsoleProgressReporter def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="embodirun") - parser.add_argument("--config", required=True, help="deployment YAML path") + parser.add_argument("--config", help="deployment YAML path (required for deployment commands)") parser.add_argument( "--state-dir", type=Path, help=("local state directory (default: ~/.local/state/embodirun, kept for compatibility)"), ) commands = parser.add_subparsers(dest="command", required=True) + example.register(commands) validate.register(commands) probe.register(commands) init.register(commands) @@ -44,6 +45,14 @@ def main( ) -> int: parser = build_parser() args = parser.parse_args(argv) + if args.command == "example": + try: + return example.run(args) + except (OSError, RuntimeError, ValueError) as error: + print(f"embodirun: error: {error}", file=sys.stderr) + return 2 + if args.config is None: + parser.error("--config is required for deployment commands") try: config = load_config(args.config) deployment = build_plan(config) diff --git a/src/embodirun/services/host/cli/command/example.py b/src/embodirun/services/host/cli/command/example.py new file mode 100644 index 0000000..3714037 --- /dev/null +++ b/src/embodirun/services/host/cli/command/example.py @@ -0,0 +1,31 @@ +"""Expose the source checkout's shared example launcher through embodirun.""" + +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path +from typing import Any + + +def register(commands: Any) -> None: + parser = commands.add_parser("example", help="prepare and run a recipe from a source checkout") + parser.add_argument( + "example_args", nargs=argparse.REMAINDER, help="init TEMPLATE [--output DIR], or MANIFEST COMMAND [options]" + ) + parser.set_defaults(command_handler=run) + + +def run(args: argparse.Namespace) -> int: + """Use exactly the launcher used by examples/run.sh, without a second dispatcher.""" + candidates = [Path(__file__).resolve().parents[6], Path.cwd()] + if source := os.environ.get("EMBODIRUN_SOURCE_ROOT"): + candidates.insert(0, Path(source).expanduser()) + for root in candidates: + if (root / "examples/runner.py").is_file() and (root / "pyproject.toml").is_file(): + sys.path.insert(0, str(root)) + from examples.runner import main + + return main(args.example_args) + raise RuntimeError("Recipe sources are missing. Install from a checkout or set EMBODIRUN_SOURCE_ROOT to it.") diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..107a105 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Local test package for imports shared by integration tests.""" diff --git a/tests/test_astra_cooperative.py b/tests/test_astra_cooperative.py index fd09c4b..3bbbf2f 100644 --- a/tests/test_astra_cooperative.py +++ b/tests/test_astra_cooperative.py @@ -13,7 +13,7 @@ from agents.astra_pi05.cooperative import CooperativeLoop, Proposal from embodirun.client import Observation, UnsupportedOperation -from test_agent_client import _client_server, _close +from tests.test_agent_client import _client_server, _close NAMES = tuple(f"joint_{index}" for index in range(12)) diff --git a/tests/test_control_takeover.py b/tests/test_control_takeover.py index b928928..43060d8 100644 --- a/tests/test_control_takeover.py +++ b/tests/test_control_takeover.py @@ -13,7 +13,7 @@ from embodirun.services.control.devices import DeviceManager from embodirun.services.control.server import ControlHttpServer, ControlService, ControlTaskRejected from embodirun.services.control.teleop import ControlHttpTeleopClient -from test_control_services import _unused_loopback_port, control_config, task_request +from tests.test_control_services import _unused_loopback_port, control_config, task_request @pytest.fixture diff --git a/tests/test_example_runner.py b/tests/test_example_runner.py index 60130a6..dd29657 100644 --- a/tests/test_example_runner.py +++ b/tests/test_example_runner.py @@ -1,13 +1,18 @@ """Exercise example dispatch and safety gates without hardware or optional SDKs.""" +import contextlib import json import os +import signal +import socket +import subprocess import sys +import threading import time import pytest import yaml -from examples import runner +from examples import configuration, runner CONFIGS = [ "multi_robot_serving/example.yaml", @@ -85,11 +90,84 @@ def test_microduck_argv_paths_and_legacy_env_are_explicit(tmp_path): assert "--check-only" in command assert command[command.index("--output") + 1] == str(tmp_path / "result") assert command[command.index("--manifest") + 1] == str(example.path.parent / "assets.md5.json") - assert command[0].endswith(".venv-microduck/bin/python") + assert command[0] == example.python assert env["MUJOCO_GL"] == "egl" assert "integrations/microduck_vln/src" in env["PYTHONPATH"] +@pytest.mark.parametrize("as_json", [False, True]) +def test_microduck_software_check_needs_no_assets_and_never_starts_a_launcher(tmp_path, monkeypatch, capsys, as_json): + path = configuration.initialize("microduck", tmp_path / "recipe with spaces") + data = yaml.safe_load(path.read_text()) + data["python"] = sys.executable + path.write_text(yaml.safe_dump(data)) + + def metadata(python, packages): + assert "embodirun-microduck" in packages + assert not {"torch", "mujoco", "numpy", "Pillow"}.intersection(packages) + return {"version": [3, 12, 3], "packages": dict.fromkeys(packages, "installed"), "sdk_src": None} + + monkeypatch.setattr(configuration, "_environment_details", metadata) + monkeypatch.setattr(runner, "Supervisor", lambda *a, **k: pytest.fail("software check opened a launcher")) + monkeypatch.setattr(runner, "execute", lambda *a, **k: pytest.fail("software check started a scene")) + arguments = [str(path), "check", "--mode", "software", *(["--json"] if as_json else [])] + assert runner.main(arguments) == 0 + output = capsys.readouterr() + assert output.err == "" + if as_json: + report = json.loads(output.out) + assert report["mode"] == "software" and report["issues"] == [] + assert any("CUDA/EGL" in item for item in report["unverified"]) + assert any("External scene" in item for item in report["unverified"]) + else: + assert "scene/CUDA/model was not tested" in output.out + assert not (path.parent / "runs").exists() + + +@pytest.mark.parametrize( + "template,mode,message", + [ + ("microduck", "hardware", "MicroDuck setup/check mode must be software or simulation"), + ("microduck", "unknown", "MicroDuck setup/check mode must be software or simulation"), + ("xlerobot", "simulation", "XLeRobot setup/check mode must be software or hardware"), + ], +) +def test_recipe_mode_errors_are_actionable_json_before_a_process_starts( + tmp_path, monkeypatch, capsys, template, mode, message +): + path = configuration.initialize(template, tmp_path / "recipe") + monkeypatch.setattr(runner.subprocess, "Popen", lambda *a, **k: pytest.fail("mode error started a process")) + assert runner.main([str(path), "check", "--mode", mode, "--json"]) == 2 + output = capsys.readouterr() + assert output.err == "" + report = json.loads(output.out) + assert report["status"] == "needs_attention" + assert message in report["issues"][0]["message"] + + +def test_microduck_software_missing_environment_explains_the_matching_setup(tmp_path, capsys): + path = configuration.initialize("microduck", tmp_path / "recipe") + data = yaml.safe_load(path.read_text()) + data["python"] = str(tmp_path / "missing environment/bin/python") + path.write_text(yaml.safe_dump(data)) + assert runner.main([str(path), "check", "--mode", "software", "--json"]) == 2 + report = json.loads(capsys.readouterr().out) + assert [item["code"] for item in report["issues"]] == ["python_missing"] + assert "setup --mode software" in report["issues"][0]["next_action"] + + +def test_microduck_simulation_json_rejects_an_empty_inference_directory(tmp_path): + path = configuration.initialize("microduck", tmp_path / "recipe") + example = runner.load_example(path) + inference = tmp_path / "empty inference directory" + inference.mkdir() + example.parameters["inference_root"] = str(inference) + report = configuration.check_report(example, mode="simulation") + issue = next(item for item in report["issues"] if item["code"] == "inference_source_missing") + assert "embodiinfer/__init__.py" in issue["message"] + assert "submodule update --init third_party/embodiinfer" in issue["next_action"] + + def test_failed_rollout_stops_deployment_and_records_failure(tmp_path, monkeypatch): example = runner.load_example(runner.ROOT / "examples" / CONFIGS[0]) deployment = tmp_path / "deployment.yaml" @@ -114,10 +192,46 @@ def fail(*args, **kwargs): report = json.loads(next((tmp_path / "outputs").glob("*/run.json")).read_text()) assert report["status"] == "failed" assert report["cleanup_returncode"] == 0 - output = next((tmp_path / "outputs").iterdir()) + output = next(path for path in (tmp_path / "outputs").iterdir() if path.is_dir()) assert (output / report["input_snapshots"][str(deployment)]).read_text() == deployment.read_text() +def test_rollout_down_still_stops_host_when_launcher_pointer_is_stale(tmp_path, monkeypatch, capsys): + source = runner.ROOT / "examples" / CONFIGS[0] + data = yaml.safe_load(source.read_text()) + data["output_dir"] = str(tmp_path / "outputs") + data["parameters"]["deployment"] = str(source.parent / "deployment.yaml") + path = tmp_path / "example.yaml" + path.write_text(yaml.safe_dump(data)) + example = runner.Example(path, data) + + def stale_stop(_output): + raise RuntimeError("stale pointer") + + monkeypatch.setattr(runner, "stop_owned", stale_stop) + monkeypatch.setattr(runner.subprocess, "check_output", lambda *a, **k: "test-revision\n") + + class FakeSupervisor: + def __init__(self, *args): + self.cancelled = type("Cancelled", (), {"is_set": lambda self: False})() + + def __enter__(self): + return self + + def __exit__(self, *args): + return False + + monkeypatch.setattr(runner, "Supervisor", FakeSupervisor) + executed = [] + monkeypatch.setattr(runner, "execute", lambda commands, *a, **k: executed.extend(commands)) + + assert runner.main([str(path), "down"]) == 2 + assert executed == [example.host("down")] + assert "Local launcher stop unconfirmed: stale pointer" in capsys.readouterr().err + report = json.loads(next((tmp_path / "outputs").glob("*/run.json")).read_text()) + assert report["status"] == "failed" + + def test_parallel_failure_reaps_other_local_children(tmp_path): pid_path = tmp_path / "child.pid" waiting = [ @@ -136,6 +250,93 @@ def test_parallel_failure_reaps_other_local_children(tmp_path): os.kill(int(pid_path.read_text()), 0) +@pytest.mark.parametrize("outcome", ["complete", "failure", "down"]) +def test_command_cleanup_releases_a_stubborn_descendant_listener(tmp_path, outcome): + from examples.lifecycle import Supervisor, stop_owned + + ready = tmp_path / "descendant.json" + descendant = r""" +import json, os, pathlib, signal, socket, sys +signal.signal(signal.SIGTERM, signal.SIG_IGN) +signal.signal(signal.SIGINT, signal.SIG_IGN) +listener = socket.socket() +listener.bind(('127.0.0.1', 0)) +listener.listen() +pathlib.Path(sys.argv[1]).write_text(json.dumps({'pid': os.getpid(), 'port': listener.getsockname()[1]})) +while True: + connection, _ = listener.accept() + connection.close() +""" + wait_ready = ( + "import pathlib, time; ready=pathlib.Path(" + repr(str(ready)) + "); deadline=time.monotonic()+5\n" + "while not ready.exists() and time.monotonic()= deadline: + pytest.fail("launcher cleanup left its descendant listener running") + time.sleep(0.01) + finally: + other.terminate() + other.wait(timeout=5) + if ready.exists(): + with contextlib.suppress(ProcessLookupError): + os.kill(json.loads(ready.read_text())["pid"], signal.SIGKILL) + if stopper is not None: + stopper.join(timeout=5) + + def test_slurm_allocation_uses_yaml_and_is_not_nested(tmp_path, monkeypatch): example = runner.load_example(runner.ROOT / "examples" / CONFIGS[-2]) example.parameters["slurm"] = "auto" diff --git a/tests/test_example_setup.py b/tests/test_example_setup.py new file mode 100644 index 0000000..50c22f4 --- /dev/null +++ b/tests/test_example_setup.py @@ -0,0 +1,272 @@ +"""Exercise recipe preparation, CLI compatibility and owned-process cancellation.""" + +import json +import os +import shlex +import signal +import subprocess +import sys +import time + +import pytest +import yaml +from examples import lifecycle, runner +from examples.configuration import TEMPLATES, _recipe_command, check_requirements, initialize +from examples.lifecycle import Supervisor, stop_owned +from examples.setup_environment import install_commands + +from embodirun.services.host.cli.cli import main as host_main + + +@pytest.mark.parametrize("template", TEMPLATES) +def test_generated_recipe_resolves_references_outside_checkout(template, tmp_path, monkeypatch): + destination = tmp_path / "a folder with spaces" / template + path = initialize(template, destination) + monkeypatch.chdir(tmp_path) + example = runner.load_example(path) + assert example.output == destination / "runs" + assert runner.main([str(path), "validate"]) == 0 + assert runner.main([str(path), "plan"]) == 0 + if template == "xlerobot": + hardware = json.loads((destination / "hardware.local.json").read_text()) + assert hardware["allow_motion"] is False + assert hardware["camera_roles_confirmed"] is False + task = json.loads(example.resolve(example.parameters["config"]).read_text()) + assert all((destination / ref).is_file() for ref in task["navigation"]["routes"].values()) + + +def test_init_never_overwrites_existing_user_configuration(tmp_path): + target = tmp_path / "robot" + target.mkdir() + calibration = target / "hardware.local.json" + calibration.write_text("operator calibration") + with pytest.raises(FileExistsError): + initialize("xlerobot", target) + assert calibration.read_text() == "operator calibration" + assert list(target.iterdir()) == [calibration] + + +def test_microduck_asset_root_derives_paths_and_retains_inventory(tmp_path): + assets = tmp_path / "external assets" + path = initialize("microduck", tmp_path / "recipe", assets=assets) + example = runner.load_example(path) + assert example.parameters["checkpoint"] == str(assets / "lightnav_sft_v3/merged") + assert example.parameters["episodes"] == str(assets / "data/demo_microduck_vln.jsonl") + assert ( + example.resolve(example.parameters["manifest"]).read_bytes() + == (runner.ROOT / "examples/microduck_vln/assets.md5.json").read_bytes() + ) + + +def test_check_lists_hardware_preconditions_without_opening_devices(tmp_path, monkeypatch, capsys): + path = initialize("xlerobot", tmp_path / "robot") + monkeypatch.setattr(runner, "execute", lambda *a, **k: pytest.fail("check started a child")) + assert runner.main([str(path), "check"]) == 2 + output = capsys.readouterr().err + assert "calibration" in output + assert "camera_roles_confirmed" in output + assert "software fixture" in output + assert not (path.parent / "runs").exists() + + +def test_check_accepts_inline_calibration_and_camera_indices_or_streams(tmp_path): + path = initialize("xlerobot", tmp_path / "robot") + hardware_path = path.parent / "hardware.local.json" + hardware = json.loads(hardware_path.read_text()) + hardware["calibration"] = {"motors": {}} + hardware["cameras"] = { + "front": 0, + "left_wrist": "rtsp://camera.local/live", + "right_wrist": None, + } + hardware_path.write_text(json.dumps(hardware)) + problems = check_requirements(runner.load_example(path)) + assert any("camera role right_wrist has no source" in item for item in problems) + assert not any("calibration does not exist" in item for item in problems) + assert not any("camera role front does not exist" in item for item in problems) + assert not any("camera role left_wrist does not exist" in item for item in problems) + + +def test_rollout_check_reports_exact_placeholder_location(): + example = runner.load_example(runner.ROOT / "examples/multi_robot_serving/example.yaml") + problems = check_requirements(example) + assert any("nodes.gpu.connection.host" in item for item in problems) + assert any("robots.arm-1.calibration_dir" in item for item in problems) + + +def test_cli_and_shell_launcher_have_the_same_validation_output(tmp_path): + path = initialize("xlerobot", tmp_path / "recipe") + cli = subprocess.run( + [ + sys.executable, + "-c", + "from embodirun.services.host.cli.cli import main; raise SystemExit(main())", + "example", + str(path), + "validate", + ], + capture_output=True, + text=True, + check=True, + ) + shell = subprocess.run( + ["bash", str(runner.ROOT / "examples/run.sh"), str(path), "validate"], + env={**os.environ, "EMBODIRUN_EXAMPLE_PYTHON": sys.executable}, + cwd=tmp_path, + capture_output=True, + text=True, + check=True, + ) + assert cli.stdout == shell.stdout + + +def test_deployment_cli_still_requires_config(): + with pytest.raises(SystemExit) as error: + host_main(["validate"]) + assert error.value.code == 2 + + +@pytest.mark.parametrize("installed_cli", [True, False]) +def test_printed_recipe_hint_preserves_a_transient_scene_python(tmp_path, monkeypatch, installed_cli): + caller = tmp_path / "caller environment/bin/python" + caller.parent.mkdir(parents=True) + if installed_cli: + caller.with_name("embodirun").touch() + scene = str(tmp_path / "scene environment/bin/python") + monkeypatch.setattr(sys, "executable", str(caller)) + monkeypatch.setenv("EMBODIRUN_SCENE_PYTHON", scene) + hint = shlex.split(_recipe_command(tmp_path / "recipe with spaces/example.yaml", "check", "--mode", "software")) + assert hint[:3] == ["env", f"EMBODIRUN_SOURCE_ROOT={runner.ROOT}", f"EMBODIRUN_SCENE_PYTHON={scene}"] + if installed_cli: + assert hint[3] == str(caller.with_name("embodirun")) + else: + assert hint[3:9] == ["uv", "run", "--frozen", "--project", str(runner.ROOT), "embodirun"] + + +def test_profile_setup_does_not_need_assets_and_uses_one_dependency_source(tmp_path): + example = runner.load_example(runner.ROOT / "examples/microduck_vln/example.yaml") + command = runner.commands(example, "setup", tmp_path)[0][0] + assert command[2] == "microduck" + for profile in ("snack", "microduck"): + commands = install_commands(profile, tmp_path / profile) + assert len(commands) == 1 + assert "--frozen" in commands[0] + assert commands[0][commands[0].index("--python") + 1] == "3.12" + project = "xlerobot_snack_delivery" if profile == "snack" else "microduck_vln" + assert commands[0][commands[0].index("--project") + 1] == str(runner.ROOT / "examples" / project) + assert commands[0][-2:] == ["--extra", "hardware" if profile == "snack" else "simulation"] + software = install_commands(profile, tmp_path / profile, mode="software") + assert len(software) == 1 + assert "--extra" not in software[0] + assert software[0][software[0].index("--project") + 1] == commands[0][commands[0].index("--project") + 1] + + +def test_duplicate_launch_does_not_remove_first_owners_socket(tmp_path): + with Supervisor(tmp_path, "run") as owner: + with pytest.raises(RuntimeError, match="already running"), Supervisor(tmp_path, "run"): + pass + assert owner.pointer.exists() + + +def test_stale_stop_socket_does_not_signal_any_pid(tmp_path, monkeypatch): + (tmp_path / ".launcher-run.json").write_text(json.dumps({"socket": str(tmp_path / "missing.sock")})) + monkeypatch.setattr(os, "kill", lambda *args: pytest.fail("must not signal a stored PID")) + with pytest.raises(RuntimeError, match="Cannot confirm"): + stop_owned(tmp_path) + + +def test_stale_run_does_not_skip_stopping_a_live_up_launcher(tmp_path, monkeypatch, capsys): + (tmp_path / ".launcher-run.json").write_text("{bad json") + (tmp_path / ".launcher-up.json").write_text(json.dumps({"socket": "/private/up.sock"})) + contacted = [] + + class FakeClient: + def __enter__(self): + return self + + def __exit__(self, *args): + pass + + def settimeout(self, value): + pass + + def connect(self, endpoint): + contacted.append(endpoint) + + def sendall(self, value): + assert value == b"stop\n" + + def recv(self, count): + return b"exited\n" + + monkeypatch.setattr(lifecycle.socket, "socket", lambda *args: FakeClient()) + with pytest.raises(RuntimeError, match="Cannot confirm run cleanup"): + stop_owned(tmp_path) + assert contacted == ["/private/up.sock"] + assert "up: launcher finished" in capsys.readouterr().out + + +@pytest.mark.parametrize("stop_method", ["down", "sigterm"]) +def test_stop_waits_for_cleanup_and_leaves_no_owned_children(tmp_path, stop_method): + script = r""" +import os, pathlib, signal, subprocess, sys, time +from examples.lifecycle import Supervisor +root = pathlib.Path(sys.argv[1]) +with Supervisor(root, 'run') as supervisor: + child = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(30)']) + (root / 'child').write_text(str(child.pid)) + (root / 'ready').touch() + try: + while not supervisor.cancelled.wait(.02): + pass + finally: + child.terminate() + child.wait(timeout=5) + time.sleep(.1) + (root / 'cleaned').touch() +""" + process = subprocess.Popen([sys.executable, "-c", script, str(tmp_path)], cwd=runner.ROOT) + try: + deadline = time.monotonic() + 10 + while not (tmp_path / "ready").exists(): + if process.poll() is not None or time.monotonic() > deadline: + pytest.fail("launcher did not start") + time.sleep(0.02) + if stop_method == "down": + stop_owned(tmp_path) + assert (tmp_path / "cleaned").exists() + else: + process.send_signal(signal.SIGTERM) + assert process.wait(timeout=10) == 0 + with pytest.raises(ProcessLookupError): + os.kill(int((tmp_path / "child").read_text()), 0) + assert not (tmp_path / ".launcher-run.json").exists() + finally: + if process.poll() is None: + process.kill() + process.wait() + + +def test_down_works_even_if_referenced_configuration_was_removed(tmp_path): + path = initialize("xlerobot", tmp_path / "recipe") + (path.parent / "config.local.json").unlink() + assert runner.main([str(path), "down"]) == 0 + + +def test_generated_xlerobot_dry_run_uses_fixtures_without_git_metadata(tmp_path, monkeypatch): + path = initialize("xlerobot", tmp_path / "recipe") + # Exercise fixture dispatch and reporting with the test interpreter, independently of setup. + manifest = yaml.safe_load(path.read_text()) + manifest["python"] = sys.executable + path.write_text(yaml.safe_dump(manifest, sort_keys=False)) + monkeypatch.delenv("EMBODIRUN_SCENE_PYTHON", raising=False) + monkeypatch.delenv("EMBODIRUN_REVISION", raising=False) + + def no_git(*args, **kwargs): + raise subprocess.CalledProcessError(128, "git") + + monkeypatch.setattr(runner.subprocess, "check_output", no_git) + assert runner.main([str(path), "dry-run"]) == 0 + report = json.loads(next((path.parent / "runs").glob("*/run.json")).read_text()) + assert report["status"] == "complete" + assert report["revision"] is None diff --git a/tests/test_services.py b/tests/test_services.py index 0da5b94..4cdd10d 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -1899,7 +1899,7 @@ def factory(_node): assert init_exit_code == 0 assert init_output.err == "" assert "Init deployment thor-so101-pi05" in init_output.out - assert "2 environments ready" in init_output.out + assert "2 environments ready" in " ".join(init_output.out.split()) initialized = StateStore(tmp_path / "thor-so101-pi05.json").load() assert initialized is not None assert initialized.inference_commit == INFERENCE_REVISION diff --git a/tests/test_snack_http_integration.py b/tests/test_snack_http_integration.py index 8b5fd25..5951a08 100644 --- a/tests/test_snack_http_integration.py +++ b/tests/test_snack_http_integration.py @@ -24,7 +24,7 @@ from embodirun.robots.lerobot.xlerobot.units import ARM_UNITS, stamped_metadata from embodirun.services.control.devices import DeviceManager from embodirun.services.control.server import ControlHttpServer, ControlService -from test_xlerobot_external_owner import _close_control, _OwnerHandler, _OwnerState, _port +from tests.test_xlerobot_external_owner import _close_control, _OwnerHandler, _OwnerState, _port class ScopeState(_OwnerState): diff --git a/tests/test_xlerobot_recipe_onboarding.py b/tests/test_xlerobot_recipe_onboarding.py new file mode 100644 index 0000000..230556c --- /dev/null +++ b/tests/test_xlerobot_recipe_onboarding.py @@ -0,0 +1,150 @@ +"""Regress installation/dispatch drift and explainable read-only recipe checks.""" + +import json +import sys +from pathlib import Path + +import pytest +import yaml +from examples import configuration, runner +from examples.configuration import initialize +from examples.xlerobot_snack_delivery import run as recipe + + +def _select_python(path, python): + data = yaml.safe_load(path.read_text()) + data["python"] = str(python) + path.write_text(yaml.safe_dump(data)) + + +def _installed_metadata(python, packages): + return {"version": [3, 12, 11], "packages": dict.fromkeys(packages, "installed"), "sdk_src": None} + + +@pytest.mark.parametrize("selection", ["manifest", "environment"]) +def test_setup_installs_into_the_same_venv_used_by_scene(selection, tmp_path, monkeypatch): + path = initialize("xlerobot", tmp_path / "recipe") + environment = tmp_path / "custom venv" + (environment / "bin").mkdir(parents=True) + (environment / "pyvenv.cfg").write_text("home = fixture\n") + python = environment / "bin/python" + python.symlink_to(sys.executable) + if selection == "manifest": + _select_python(path, python) + monkeypatch.setenv("EMBODIRUN_SCENE_PYTHON", "/ignored/bin/python") + else: + monkeypatch.setenv("EMBODIRUN_SCENE_PYTHON", str(python)) + example = runner.load_example(path) + setup = runner.commands(example, "setup", tmp_path, mode="software")[0][0] + scene = runner.commands(example, "dry-run", tmp_path)[0][0] + assert setup[setup.index("--environment") + 1] == str(environment) + assert scene[0] == str(python) + + +def test_system_python_cannot_be_an_install_target(tmp_path, monkeypatch): + path = initialize("xlerobot", tmp_path / "recipe") + monkeypatch.setenv("EMBODIRUN_SCENE_PYTHON", "/usr/bin/python3") + with pytest.raises(ValueError, match="not a virtual environment"): + runner.commands(runner.load_example(path), "setup", tmp_path) + + +def test_deployment_ports_drive_check_and_control_clients_without_moving_routes(tmp_path, monkeypatch): + path = initialize("xlerobot", tmp_path / "recipe") + _select_python(path, sys.executable) + task = path.parent / "config.local.json" + assert "endpoints" not in json.loads(task.read_text())["control"] + deployment = path.parent / "deployment.local.yaml" + values = yaml.safe_load(deployment.read_text()) + values["control"] = {"base_port": 8210, "manipulation_port": 8211} + deployment.write_text(yaml.safe_dump(values)) + config = recipe.load_recipe_config(task, deployment) + connected = [] + monkeypatch.setattr("embodirun.client.ControlClient", lambda endpoint, **kw: connected.append(endpoint)) + recipe.ControlRuntimes(config) + monkeypatch.setattr(configuration, "_environment_details", _installed_metadata) + report = configuration.check_report(runner.load_example(path), mode="software") + assert connected == ["http://127.0.0.1:8210", "http://127.0.0.1:8211"] + assert [item["endpoint"] for item in report["control_endpoints"].values()] == connected + assert Path(config["_config_dir"]) == task.parent + assert all((task.parent / ref).is_file() for ref in config["navigation"]["routes"].values()) + + +def test_standalone_config_rehearsal_keeps_legacy_endpoint_support(tmp_path, monkeypatch): + monkeypatch.setattr("embodirun.client.ControlClient", lambda *a, **kw: pytest.fail("connected to Control")) + task = runner.ROOT / "examples/xlerobot_snack_delivery/config.example.json" + assert recipe.main(["--config", str(task), "--output", str(tmp_path), "--mode", "dry-run", "--auto-confirm"]) == 0 + status = json.loads((tmp_path / "status.json").read_text()) + assert status["physical_success"] is None + assert status["task_success"] == "unverified" + + +def test_software_check_accepts_fixtures_hardware_check_explains_missing_inputs(tmp_path, monkeypatch, capsys): + path = initialize("xlerobot", tmp_path / "recipe") + _select_python(path, sys.executable) + monkeypatch.setattr(configuration, "_environment_details", _installed_metadata) + monkeypatch.setattr(runner, "execute", lambda *a, **kw: pytest.fail("started a child service")) + assert runner.main([str(path), "check", "--mode", "software", "--json"]) == 0 + software = json.loads(capsys.readouterr().out) + assert software["issues"] == [] + assert software["unverified"] + assert runner.main([str(path), "check", "--json"]) == 2 + hardware = json.loads(capsys.readouterr().out) + assert {item["code"] for item in hardware["issues"]} >= {"fixture_route", "sdk_missing", "calibration_missing"} + assert all(item["location"] and item["next_action"] for item in hardware["issues"]) + assert not (path.parent / "runs").exists() + + +def test_json_check_reports_an_unreadable_manifest_as_one_actionable_object(tmp_path, capsys): + path = tmp_path / "missing.yaml" + assert runner.main([str(path), "check", "--mode", "software", "--json"]) == 2 + captured = capsys.readouterr() + report = json.loads(captured.out) + assert report["status"] == "needs_attention" + assert report["issues"][0]["next_action"] + assert captured.err == "" + + +def test_json_check_reports_malformed_deployment_ports_without_a_traceback(tmp_path, capsys): + path = initialize("xlerobot", tmp_path / "recipe") + deployment = path.parent / "deployment.local.yaml" + values = yaml.safe_load(deployment.read_text()) + values["control"] = [] + deployment.write_text(yaml.safe_dump(values)) + assert runner.main([str(path), "check", "--mode", "software", "--json"]) == 2 + captured = capsys.readouterr() + assert "deployment.control" in json.loads(captured.out)["issues"][0]["message"] + assert captured.err == "" + + +def test_microduck_missing_python_action_uses_its_supported_setup_command(tmp_path): + path = initialize("microduck", tmp_path / "recipe") + _select_python(path, tmp_path / "missing-venv/bin/python") + report = configuration.check_report(runner.load_example(path)) + action = next(item["next_action"] for item in report["issues"] if item["code"] == "python_missing") + assert action.endswith(" setup") + assert "--mode" not in action + + +def test_existing_init_directory_prints_a_reuse_action_without_changing_files(tmp_path, capsys): + directory = tmp_path / "recipe" + path = initialize("xlerobot", directory) + original = path.read_bytes() + assert runner.main(["init", "xlerobot", "--output", str(directory)]) == 2 + assert path.read_bytes() == original + assert str(path) in capsys.readouterr().err + + +def test_refused_launch_records_failure_and_prints_the_retry_without_starting_services(tmp_path, monkeypatch, capsys): + path = initialize("xlerobot", tmp_path / "recipe") + + def refused(*args): + raise RuntimeError("this recipe already has a running launcher") + + monkeypatch.setattr(runner, "Supervisor", refused) + monkeypatch.setattr(runner, "execute", lambda *a, **kw: pytest.fail("started a child service")) + assert runner.main([str(path), "setup", "--mode", "software"]) == 2 + report_path = next((path.parent / "runs").glob("*/run.json")) + assert json.loads(report_path.read_text())["status"] == "failed" + captured = capsys.readouterr() + assert str(report_path.parent) in captured.err + assert "Next:" in captured.err and "setup --mode software" in captured.err diff --git a/tests/xlerobot_owner/test_drive_speed.py b/tests/xlerobot_owner/test_drive_speed.py index 7c24acf..1314e8e 100644 --- a/tests/xlerobot_owner/test_drive_speed.py +++ b/tests/xlerobot_owner/test_drive_speed.py @@ -1,13 +1,13 @@ import asyncio import pytest -from tests.xlerobot_owner.test_hardware import _config -from tests.xlerobot_owner.test_keyboard import enable, packet -from tests.xlerobot_owner.test_server import HEADERS, station, wait_for from embodirun_xlerobot_owner.control import MappingConfig from embodirun_xlerobot_owner.hardware import HardwareRobot from embodirun_xlerobot_owner.keyboard import KeyboardFrame +from tests.xlerobot_owner.test_hardware import _config +from tests.xlerobot_owner.test_keyboard import enable, packet +from tests.xlerobot_owner.test_server import HEADERS, station, wait_for async def set_speed(ws, linear=0.1, angular=20): diff --git a/tests/xlerobot_owner/test_dualsense_recording.py b/tests/xlerobot_owner/test_dualsense_recording.py index f51b742..30bb8f5 100644 --- a/tests/xlerobot_owner/test_dualsense_recording.py +++ b/tests/xlerobot_owner/test_dualsense_recording.py @@ -5,11 +5,10 @@ pytest.importorskip("PIL") -from tests.xlerobot_owner.test_dualsense import FakeRobot, sample - from embodirun_xlerobot_owner.dualsense_drive import ZERO, BaseSession from embodirun_xlerobot_owner.dualsense_recording import DemoRecording, input_record from embodirun_xlerobot_owner.robot import DemoRobot +from tests.xlerobot_owner.test_dualsense import FakeRobot, sample class Observer: diff --git a/tests/xlerobot_owner/test_head_tilt.py b/tests/xlerobot_owner/test_head_tilt.py index 39840e4..8e4c966 100644 --- a/tests/xlerobot_owner/test_head_tilt.py +++ b/tests/xlerobot_owner/test_head_tilt.py @@ -1,8 +1,7 @@ import time -from tests.xlerobot_owner.test_hardware import _config - from embodirun_xlerobot_owner.hardware import HEAD_TILT_NAME, HardwareRobot +from tests.xlerobot_owner.test_hardware import _config def station(tmp_path): diff --git a/tests/xlerobot_owner/test_keyboard.py b/tests/xlerobot_owner/test_keyboard.py index 7cea078..7433909 100644 --- a/tests/xlerobot_owner/test_keyboard.py +++ b/tests/xlerobot_owner/test_keyboard.py @@ -1,15 +1,16 @@ import asyncio +import shutil import subprocess import time from pathlib import Path import pytest -from tests.xlerobot_owner.test_hardware import _config -from tests.xlerobot_owner.test_server import HEADERS, station, wait_for from embodirun_xlerobot_owner.control import InputClock, MappingConfig from embodirun_xlerobot_owner.keyboard import KeyboardFrame from embodirun_xlerobot_owner.robot import DemoRobot +from tests.xlerobot_owner.test_hardware import _config +from tests.xlerobot_owner.test_server import HEADERS, station, wait_for def packet(seq=0, keys=(), **extra): @@ -193,6 +194,9 @@ def test_base_only_hardware_arm_drive_stop_do_not_write_arm_registers(tmp_path): def test_browser_key_state_machine(): + node = shutil.which("node") + if not node: + pytest.skip("Node required for browser key state-machine test") source = Path(__file__).parents[2] / "integrations/xlerobot_owner/src/embodirun_xlerobot_owner/web/drive.js" script = """ const assert = require('node:assert/strict'); @@ -209,7 +213,7 @@ def test_browser_key_state_machine(): k.key('KeyW', true, true); assert.equal(k.keys.size, 0); k.enable(); assert.equal(k.keys.size, 0); assert.equal(k.key('KeyX', true), false); """ - subprocess.run(["node", "-e", script, str(source)], check=True) + subprocess.run([node, "-e", script, str(source)], check=True) @pytest.mark.asyncio diff --git a/tests/xlerobot_owner/test_motor_diagnostics.py b/tests/xlerobot_owner/test_motor_diagnostics.py index d38e825..fcdb22f 100644 --- a/tests/xlerobot_owner/test_motor_diagnostics.py +++ b/tests/xlerobot_owner/test_motor_diagnostics.py @@ -1,10 +1,10 @@ import copy import pytest -from tests.xlerobot_owner.test_hardware import _robot -from tests.xlerobot_owner.test_server import HEADERS, station from embodirun_xlerobot_owner.motor_diagnostics import read_sts3215_present_block +from tests.xlerobot_owner.test_hardware import _robot +from tests.xlerobot_owner.test_server import HEADERS, station def word(value): diff --git a/tests/xlerobot_owner/test_parallel_control.py b/tests/xlerobot_owner/test_parallel_control.py index fe1c61c..31af547 100644 --- a/tests/xlerobot_owner/test_parallel_control.py +++ b/tests/xlerobot_owner/test_parallel_control.py @@ -1,10 +1,10 @@ import asyncio import pytest -from tests.xlerobot_owner.test_server import HEADERS, TOKEN, station from embodirun_xlerobot_owner.control import JOINT_NAMES from embodirun_xlerobot_owner.robot import RemoteRobot +from tests.xlerobot_owner.test_server import HEADERS, TOKEN, station def headers(scope, owner): diff --git a/tests/xlerobot_owner/test_powered_wheel_handoff.py b/tests/xlerobot_owner/test_powered_wheel_handoff.py index 1f1fb30..ba11110 100644 --- a/tests/xlerobot_owner/test_powered_wheel_handoff.py +++ b/tests/xlerobot_owner/test_powered_wheel_handoff.py @@ -1,9 +1,9 @@ import time import pytest -from tests.xlerobot_owner.test_hardware import _held_snapshot, _robot from embodirun_xlerobot_owner.hardware import WHEEL_NAMES +from tests.xlerobot_owner.test_hardware import _held_snapshot, _robot def station(tmp_path, monkeypatch): diff --git a/tests/xlerobot_owner/test_recording.py b/tests/xlerobot_owner/test_recording.py index 320d40d..ab42e7d 100644 --- a/tests/xlerobot_owner/test_recording.py +++ b/tests/xlerobot_owner/test_recording.py @@ -495,5 +495,5 @@ def missing_lerobot(name: str): return original_import(name) monkeypatch.setattr(recording_module.importlib, "import_module", missing_lerobot) - with pytest.raises(LeRobotExportError, match="install.*lerobot"): + with pytest.raises(LeRobotExportError, match="install.*recording extra"): export_lerobot([episode], repo_id="user/no-lerobot", output=tmp_path / "missing") diff --git a/tests/xlerobot_owner/test_stop_burst.py b/tests/xlerobot_owner/test_stop_burst.py index a5af10c..9771818 100644 --- a/tests/xlerobot_owner/test_stop_burst.py +++ b/tests/xlerobot_owner/test_stop_burst.py @@ -1,4 +1,5 @@ import pytest + from tests.xlerobot_owner.test_motor_diagnostics import setup from tests.xlerobot_owner.test_wheel_observe_block import forbid_wheel_scalar_reads diff --git a/tests/xlerobot_owner/test_stop_fault_handoff.py b/tests/xlerobot_owner/test_stop_fault_handoff.py index b394dba..4b13cf7 100644 --- a/tests/xlerobot_owner/test_stop_fault_handoff.py +++ b/tests/xlerobot_owner/test_stop_fault_handoff.py @@ -7,13 +7,12 @@ pytest.importorskip("aiohttp") -from tests.xlerobot_owner.test_hardware import FakeBus, _config -from tests.xlerobot_owner.test_motor_diagnostics import Packet -from tests.xlerobot_owner.test_server import HEADERS, TOKEN, station - from embodirun_xlerobot_owner.hardware import HEAD_TILT_NAME, WHEEL_NAMES, HardwareRobot from embodirun_xlerobot_owner.server import Platform from embodirun_xlerobot_owner.stop_fault_handoff import validate_snapshot +from tests.xlerobot_owner.test_hardware import FakeBus, _config +from tests.xlerobot_owner.test_motor_diagnostics import Packet +from tests.xlerobot_owner.test_server import HEADERS, TOKEN, station def prepared(tmp_path, monkeypatch, *, legacy=False, torque_fault=False): diff --git a/tests/xlerobot_owner/test_stop_window.py b/tests/xlerobot_owner/test_stop_window.py index 7523743..a2c02a3 100644 --- a/tests/xlerobot_owner/test_stop_window.py +++ b/tests/xlerobot_owner/test_stop_window.py @@ -1,10 +1,10 @@ from types import SimpleNamespace import pytest -from tests.xlerobot_owner.test_motor_diagnostics import setup -from tests.xlerobot_owner.test_stop_fault_handoff import prepared from embodirun_xlerobot_owner import hardware +from tests.xlerobot_owner.test_motor_diagnostics import setup +from tests.xlerobot_owner.test_stop_fault_handoff import prepared def clocked(robot, bus, monkeypatch, *, until=0.65, alternating=False): diff --git a/tests/xlerobot_owner/test_wheel_observe_block.py b/tests/xlerobot_owner/test_wheel_observe_block.py index d4ab8f3..f28c259 100644 --- a/tests/xlerobot_owner/test_wheel_observe_block.py +++ b/tests/xlerobot_owner/test_wheel_observe_block.py @@ -1,4 +1,5 @@ import pytest + from tests.xlerobot_owner.test_motor_diagnostics import setup