diff --git a/.env.example b/.env.example index 53710ad..a8baf2d 100644 --- a/.env.example +++ b/.env.example @@ -3,56 +3,43 @@ # --- Host / timezone --- GENERIC_TIMEZONE=Europe/Zurich -# --- n8n URL (Mac / LAN callers) --- -N8N_HOST=192.168.0.26 +# --- n8n URL (local default; use the server DNS name/IP for LAN callers) --- +N8N_HOST=localhost N8N_PORT=5678 N8N_PROTOCOL=http +N8N_BIND_ADDRESS=127.0.0.1 N8N_HOST_PORT=5678 -WEBHOOK_URL=http://192.168.0.26:5678/ +N8N_WEBHOOK_URL=http://localhost:5678/ N8N_SECURE_COOKIE=false +N8N_PROXY_HOPS=0 # Encryption key for credentials at rest. # Reusing the existing n8n_data volume: copy encryptionKey from # docker compose exec -u node n8n cat /home/node/.n8n/config -# Fresh volume: generate with openssl rand -hex 16 and re-import credentials. +# Fresh volume: `scripts/init.sh` generates a 256-bit key. N8N_ENCRYPTION_KEY= # Instance owner (provisioned via N8N_INSTANCE_OWNER_* on n8n start). -# Run ./scripts/ensure-n8n-owner.sh before `docker compose up` to bcrypt-hash -# the password into secrets/n8n_owner.env for Compose. +# The n8n entrypoint hashes N8N_OWNER_PASSWORD; n8n-reconcile logs in with +# the same values to import workflows. N8N_OWNER_EMAIL= N8N_OWNER_PASSWORD= # N8N_OWNER_FIRST_NAME=Syndicator # N8N_OWNER_LAST_NAME=Owner -# Optional override for workflow publish. When empty, bootstrap creates or -# rotates secrets/n8n_api_key (label syndicator-bootstrap). -# N8N_API_KEY= - -# Optional: owner user id for import ownership. Bootstrap auto-detects the -# global:owner user from SQLite when empty. -# N8N_OWNER_USER_ID= - -# --- SFTP (published to host; internal compose hostname is always "sftp") --- +# --- SFTP (published to host) --- +SFTP_BIND_ADDRESS=127.0.0.1 SFTP_PUBLISH_PORT=2222 +# SFTP_KEYS_DIR=./sftp/keys +# Optional client private key used by `bin/syndicator verify`. +# SFTP_CLIENT_KEY_FILE=./secrets/sftp_client_ed25519 -# --- Credential secrets (rendered into templates, then deleted) --- +# --- Credential secrets --- OPENAI_API_KEY= POSTIZ_API_KEY= -# n8n FTP/SFTP credential (host must be the compose service name) -SFTP_HOST=sftp -SFTP_USERNAME=sftp -# Path to the private key n8n uses to reach the sftp service (PEM/OpenSSH). -# Created automatically by scripts/ensure-sftp-keys.sh (also run by bootstrap). -SFTP_PRIVATE_KEY_FILE=./secrets/sftp_n8n_ed25519 - -# --- Image pins (optional; update.sh uses :stable by default) --- -# N8N_BASE_IMAGE=docker.n8n.io/n8nio/n8n:stable -# FFMPEG_IMAGE=mwader/static-ffmpeg:7.1.1 -# POSTIZ_NODE_VERSION=0.2.17 -# FFMPEG_STUDIO_NODE_VERSION=1.0.0 - -# --- update.sh --- -# UPDATE_LOG=/var/log/syndicator-update.log +# --- Build/runtime overrides (normally leave unset) --- +# SYNDICATOR_IMAGE_TAG=local +# PYAUTOFLIP_WARM_MODELS=1 +# SFTP_PLATFORM=linux/amd64 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3796659 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,31 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + + - package-ecosystem: docker-compose + directory: / + schedule: + interval: weekly + + - package-ecosystem: docker + directory: /n8n + schedule: + interval: weekly + + - package-ecosystem: docker + directory: /pyautoflip + schedule: + interval: weekly + + - package-ecosystem: npm + directory: /n8n + schedule: + interval: weekly + + - package-ecosystem: pip + directory: /pyautoflip + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8a25a92 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,73 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Validate repository manifests + run: | + python3 -m unittest discover -s tests -p 'test_*.py' + bash tests/test-init.sh + + - name: Validate shell scripts + run: | + shell_files=(bin/syndicator scripts/*.sh tests/*.sh tests/integration/*.sh sftp/*.sh n8n/*.sh) + bash -n "${shell_files[@]}" + shellcheck "${shell_files[@]}" + + - name: Render Compose configuration + run: bash tests/validate-compose.sh + + - name: Audit locked community nodes + working-directory: n8n + run: npm audit + + build-images: + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + + - name: Build application images + run: | + bash tests/validate-compose.sh build n8n pyautoflip + + - name: Exercise production reframe path + run: bash tests/integration/reframe.sh + + - name: Exercise isolated stack twice + run: bash tests/integration/stack.sh + + build-arm64: + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + - uses: docker/setup-qemu-action@v4 + - uses: docker/setup-buildx-action@v4 + + - name: Build application images for Linux arm64 + run: | + docker buildx build --platform linux/arm64 ./n8n + docker buildx build \ + --platform linux/arm64 \ + --build-arg PYAUTOFLIP_WARM_MODELS=0 \ + ./pyautoflip diff --git a/.gitignore b/.gitignore index a207e51..4935d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,15 +9,15 @@ __pycache__/ .pytest_cache/ .ruff_cache/ dist/ +node_modules/ # Secrets .env config.local.yaml .mypy_cache/ -# Compose secrets / local keys / update log +# Compose secrets / local keys secrets/* !secrets/.gitkeep sftp/keys/* !sftp/keys/.gitkeep -update.log diff --git a/README.md b/README.md index e8fb91e..db69b3d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Syndicator Syndicator takes a blog post and: -1. Generate a static web site translated to languages EN, FR, ES, SP, IT, and Pirate Speak -2. Distribute the blog post to social media platforms Instagram, Facebook, Youtube, and X. +1. Generate a static web site translated to EN, DE, ES, FR, IT, and Pirate Speak +2. Distribute the blog post to social media platforms Instagram, Facebook, YouTube, and X. It uses AI extensively for various aspects like translation, post text generation, and media cropping. @@ -24,14 +24,14 @@ ASCII context (kept for LLM / text-only readers): Syndicator provides the `syndicate` interface specified in this document. * Syndicator uses [Postiz](https://postiz.com/) to schedule social media posts. -* Syndicator uses [OpenAI](https://openai.com/) for KI tasks. +* Syndicator uses [OpenAI](https://openai.com/) for AI tasks. * Syndicator uses [Hugo](https://gohugo.io/) to generate static blog post site. ## syndicate interface Callers invoke syndicate by: -1. uploading medias to SFTP (port `2222`, key-only; authorize a public key in `sftp/keys/`, or reuse `secrets/sftp_n8n_ed25519` from `./scripts/ensure-sftp-keys.sh`) +1. uploading medias to SFTP (port `2222`, key-only; authorize a public key in `sftp/keys/`) 2. POSTing JSON to the Blog Post Publish and Reel Publish webhook. 3. The webhook responds with HTTP 2xx as soon as the request is accepted and continues asynchronously. @@ -156,34 +156,39 @@ Once Syndicator has finished processing Blog Post Publish the static Hugo post c ## Setup ```bash -cp .env.example .env -# Fill secrets: N8N_ENCRYPTION_KEY, N8N_OWNER_EMAIL, N8N_OWNER_PASSWORD, OpenAI, Postiz -./scripts/ensure-sftp-keys.sh -./scripts/ensure-n8n-owner.sh +scripts/init.sh +# Fill the values requested in .env, then: docker compose up -d --build -./scripts/bootstrap-n8n.sh +bin/syndicator verify ``` -Owner account is provisioned from env on n8n start (`N8N_INSTANCE_OWNER_*`). Bootstrap logs in with `N8N_OWNER_EMAIL` / `N8N_OWNER_PASSWORD` to create or reuse an API key at `secrets/n8n_api_key` (or uses `N8N_API_KEY` if set), then imports credentials/workflows and publishes webhooks. UI login uses the same owner credentials. +`init.sh` creates `.env` and an encryption key. Compose builds and starts the stack. `verify` reconciles n8n credentials and workflows inside Compose, then checks n8n, webhook registration, pyautoflip, and SFTP. It is safe to run repeatedly; an unchanged reconcile is skipped. -`ensure-sftp-keys.sh` writes `secrets/sftp_n8n_ed25519` (private) and `sftp/keys/n8n.pub` (public); bootstrap runs it too. `ensure-n8n-owner.sh` writes `secrets/n8n_owner.env` (bcrypt hash for Compose); bootstrap runs that as well. Extra client keys: copy any `.pub` into `sftp/keys/` and `docker compose restart sftp`. Host keys live in the `sftp_host_keys` volume (generated on first start). Connect on port `2222` as user `sftp`. +Owner account is provisioned from env on n8n start (`N8N_INSTANCE_OWNER_*`). After n8n is healthy, the `n8n-reconcile` service logs in with `N8N_OWNER_EMAIL` / `N8N_OWNER_PASSWORD`, imports credentials and workflows from git, and publishes webhooks. UI login uses the same owner credentials. -The `files-init` Compose service chowns the shared `n8n_files` volume to uid/gid `1000` on each `up` so n8n and pyautoflip can write under `/files`. -## Update Worfklows +Authorize callers by copying a `.pub` into `sftp/keys/` and recreating the SFTP service (`docker compose up -d --force-recreate sftp`). Host keys live in the `sftp_host_keys` volume (generated on first start). Connect on port `2222` as user `sftp`. -`./scripts/export-workflows.sh` exports all workflows from n8n into workflows/ folder in this repo +Published ports bind to loopback by default. Read the [operations runbook](docs/operations.md) before enabling LAN or internet access. -## Automatic updates +The `files-init` Compose service chowns the shared `n8n_files` and `sftp_data` volumes to uid/gid `1000` on each `up` so n8n, pyautoflip, and SFTP can write. -`scripts/update.sh` rebuilds with `--pull`, recreates containers, prunes old images, leaves volumes alone. Covers **n8n and pyautoflip**. +## Update workflows -Logs default to `update.log` (`UPDATE_LOG` to override). +`bin/syndicator export` exports sanitized workflows from n8n into `n8n/workflows/`. + +## Updates and recovery + +Instances are disposable. `.env`, SFTP host keys, and authorized client keys are identity; everything else can be rebuilt from git. + +Pull a reviewed revision and run `docker compose up -d --build --pull always`, then `bin/syndicator verify`. If verify fails, bring the stack down, fix the checkout, and start again. + +Disaster recovery is a new instance: reprovide `.env`, run `scripts/init.sh` and `docker compose up -d --build`, then `bin/syndicator verify`. Regenerate SFTP keys unless you kept them outside Syndicator. Callers may need to accept a new SSH host key and re-upload files. ## Architecture -The workflow engine, n8n, orchestrates all blog post processing via modular workflows. The most important non-functional requirements are automation and maintainability, as the goal is to minimize time spent managing social media platforms. The initial version of Syndicator was "custom-made" by LLMs, but quickly became unmaintainable. This experience highlighted the need to adopt a workflow engine and decompose the blog post processing into simple, easy-to-understand nodes. This approach not only streamlines debugging and scaling, but also leverages a higher-level runtime environment. +The workflow engine, n8n, orchestrates all blog post processing via modular workflows. The most important non-functional requirements are repeatability, testability, automation, and maintainability. The initial custom pipeline became difficult to change, which motivated decomposing processing into visible workflow nodes. -However, this comes with increased setup complexity—which is why everything is containerized, aiming for a "one-click" deployment to spin up new instances, including workflow instantiation and authentication setup. While achieving this seamless setup remains a work in progress, it is still uncertain whether the chosen technology stack can fully deliver on this vision. +Compose remains the application boundary because it isolates three different runtimes and provides the same topology on macOS and Linux. Instantiate with Compose; `bin/syndicator` covers verify, export, and logs. The rationale and rejected alternatives are recorded in [ADR 0001](docs/adr/0001-deployment-model.md); disposable instances are [ADR 0002](docs/adr/0002-disposable-instances.md). ## Software Design @@ -191,36 +196,47 @@ Software design is split into **instantiation** (how an instance is built and st ### Instantiation -The repo is the blueprint for a containerized instance: Compose defines the stack, scripts bootstrap credentials and import workflows, and the rest is source material those steps consume. - -| Piece | Role | -|-------|------| -| `docker-compose.yml` | Compose stack: files-init + SFTP + n8n + pyautoflip | -| `.env.example` | Env template for secrets and host paths | -| `n8n/Dockerfile` | Custom n8n image (`ffmpeg` + community node seed) | -| `sftp/Dockerfile` | atmoz/sftp wrapper (host keys volume, key sync, chown) | -| `scripts/` | ensure-sftp-keys / ensure-n8n-owner / bootstrap / export / update | -| `n8n/workflows/` | Importable workflow exports (source of truth) | -| `n8n/credentials/` | Credential templates (stable IDs; secrets from `.env`) | -| `pyautoflip/` | Image/build context for the reframe sidecar | -| `sftp/keys/` | Authorized client public keys (refreshed into `authorized_keys` on each sftp start) | -| `systemd/*` | Optional host timer for updates | +To start an instance you need this repository and a filled-in `.env`. `docker compose up --build` builds the images and starts sftp, n8n, and pyautoflip. n8n comes up with an owner account from `.env`, but not yet with Syndicator's workflows. `n8n-reconcile` then logs into that n8n, creates credentials from `.env`, imports the workflow JSON from git, and publishes the webhooks. After that the instance matches this checkout. +```mermaid +flowchart LR + Git["Git checkout"] + Env[".env"] + subgraph instantiate ["instantiate"] + Init["files-init"] + SFTP["sftp"] + N8N["n8n"] + PyAF["pyautoflip"] + Recon["n8n-reconcile"] + end + Git --> instantiate + Git -->|workflows, credential templates| Recon + Env -->|secrets, owner| N8N + Env --> Recon + Init -->|chown volumes| SFTP + Init --> N8N + Init --> PyAF + N8N -->|healthy| Recon + Recon -->|import + publish webhooks| N8N ``` -docker-compose.yml -.env.example -n8n/Dockerfile -n8n/workflows/ -n8n/credentials/*.template.json -pyautoflip/ -sftp/ -scripts/{ensure-sftp-keys,ensure-n8n-owner,bootstrap,export,update}.sh -systemd/* -``` + +| Component | Role | +|-----------|------| +| Git checkout | Blueprint: Compose file, Dockerfiles, workflow JSON, credential templates, SFTP startup hook, authorized `.pub` keys | +| `.env` | Instance identity: encryption key, owner login, API keys, bind addresses. Created from `.env.example` by `scripts/init.sh` | +| `files-init` | One-shot: chowns shared `n8n_files` and `sftp_data` to uid/gid `1000` so n8n, pyautoflip, and SFTP can write | +| `sftp` | Starts with `sftp/setup.sh`: durable host keys in `sftp_host_keys`, client keys from `sftp/keys/` | +| `n8n` | Custom image (`ffmpeg`, community nodes). On start, hashes `N8N_OWNER_PASSWORD` and provisions the owner from env. SQLite lives in `n8n_data` | +| `pyautoflip` | Custom image; shares `sftp_data` at `/syndicator` with n8n | +| `n8n-reconcile` | Compose profile `reconcile`, not a long-running service. Logs in as owner, renders credential templates from `.env`, imports workflows from git, publishes webhooks | + +`bin/syndicator verify` is the operator gate after `docker compose up`: it waits for n8n, runs reconcile, then checks health, webhook registration, pyautoflip, and SFTP. Export and logs are the other CLI commands; they are not part of instantiate. + +Git remains source of truth for workflows. n8n's volume is disposable; a new instance re-imports from git. See [ADR 0001](docs/adr/0001-deployment-model.md) and [ADR 0002](docs/adr/0002-disposable-instances.md). ### Runtime structure -Once instantiated, three services collaborate: callers reach **sftp** (files) and **n8n** (webhooks); n8n drives SFTP, **pyautoflip**, and external APIs. +Once instantiated, three services collaborate: callers reach **sftp** (files) and **n8n** (webhooks); n8n reads and writes the shared SFTP volume, **pyautoflip**, and external APIs. ```mermaid flowchart LR @@ -232,8 +248,8 @@ flowchart LR end Caller -->|key auth SFTP| SFTP Caller -->|webhooks| N8N - N8N -->|FTP host=sftp| SFTP - N8N -->|HTTP /reframe on /files| PyAF + N8N -->|shared volume /syndicator| SFTP + N8N -->|HTTP /reframe on /syndicator| PyAF N8N --> OpenAI["OpenAI"] N8N --> Postiz["Postiz"] N8N --> Hugo["Hugo site tree"] @@ -242,13 +258,13 @@ flowchart LR | Service | Role | |---------|------| | `sftp` | Key-only SFTP on port `2222`; chroot home with `/syndicator/…`; host keys in `sftp_host_keys` | -| `n8n` | Workflow engine; SQLite in `n8n_data`; shares `n8n_files` → `/files` with pyautoflip | -| `pyautoflip` | Reel reframing sidecar (`HTTP /reframe` on `/files`) | +| `n8n` | Workflow engine; SQLite in `n8n_data`; shares `n8n_files` → `/files` with pyautoflip and `sftp_data` → `/syndicator` | +| `pyautoflip` | Reel reframing sidecar (`HTTP /reframe` on `/syndicator`) | | Workflow | Role | |----------|------| -| Blog Post Publish | Webhook `/publish` → Hugo adapt + social feature adapt | -| Reel Publish | Webhook `/reel` → adapt → caption → Postiz | +| Blog Post Publish | Webhook `/webhook/publish` → Hugo adapt + social feature adapt | +| Reel Publish | Webhook `/webhook/reel` → adapt → caption → Postiz | For brevity, subworkflows invoked by these workflows are not listed here. diff --git a/bin/syndicator b/bin/syndicator new file mode 100755 index 0000000..863cd95 --- /dev/null +++ b/bin/syndicator @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +# shellcheck source=scripts/lib.sh +source "$ROOT/scripts/lib.sh" + +usage() { + cat <<'EOF' +Usage: bin/syndicator + + verify Reconcile n8n, then check health, webhooks, pyautoflip, and SFTP + export Export sanitized workflows from n8n + logs Follow Compose service logs +EOF +} + +command="${1:-help}" +shift || true + +case "$command" in + verify) + exec "$ROOT/scripts/verify.sh" "$@" + ;; + export) + exec "$ROOT/scripts/export-workflows.sh" "$@" + ;; + logs) + compose logs -f "$@" + ;; + help | --help | -h) + usage + ;; + *) + echo "Unknown command: $command" >&2 + usage >&2 + exit 2 + ;; +esac diff --git a/docker-compose.yml b/docker-compose.yml index 008a8e8..0f265d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,63 +1,59 @@ # Phase 1 syndicator stack: SFTP staging + n8n (regular/SQLite) + pyautoflip. # Usage (from this directory): -# cp .env.example .env # fill secrets -# ./scripts/ensure-sftp-keys.sh -# ./scripts/ensure-n8n-owner.sh +# scripts/init.sh # fill the requested values in .env # docker compose up -d --build -# ./scripts/bootstrap-n8n.sh +# bin/syndicator verify name: syndicator services: # Docker named volumes are root-owned on first create; n8n + pyautoflip run as uid 1000. files-init: - image: alpine:3.20 + image: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc volumes: - - n8n_files:/data + - n8n_files:/files + - sftp_data:/syndicator command: [ "sh", "-c", - "chown -R 1000:1000 /data && chmod 775 /data", + "chown 1000:1000 /files /syndicator && chmod 775 /files /syndicator", ] restart: "no" sftp: - build: ./sftp - image: syndicator-sftp:local + image: atmoz/sftp:alpine@sha256:81fa92512bf8ead4849f33c1c153907b86d32d77704d1c62a9c70b4316ae9e50 + # The upstream image is amd64-only; Docker Desktop emulates it on Apple Silicon. + platform: ${SFTP_PLATFORM:-linux/amd64} restart: unless-stopped ports: - - "${SFTP_PUBLISH_PORT:-2222}:22" + - "${SFTP_BIND_ADDRESS:-127.0.0.1}:${SFTP_PUBLISH_PORT:-2222}:22" volumes: - sftp_data:/home/sftp/syndicator - # Client public keys (ensure-sftp-keys.sh writes n8n.pub here). - - ./sftp/keys:/home/sftp/.ssh/keys:ro + # Client public keys (copy any .pub into sftp/keys/). + - ${SFTP_KEYS_DIR:-./sftp/keys}:/home/sftp/.ssh/keys:ro # Server host keys generated on first start; survive recreate. - sftp_host_keys:/etc/ssh/host_keys + # Supported atmoz startup hook; avoids replacing the sshd binary. + - ./sftp/setup.sh:/etc/sftp.d/10-syndicator.sh:ro # user::uid:gid:dirs — empty password → key-only auth - command: sftp::1001:100:syndicator + command: sftp::1000:1000:syndicator healthcheck: - test: ["CMD-SHELL", "pgrep -x sshd >/dev/null || exit 1"] - interval: 30s + test: + [ + "CMD-SHELL", + "kill -0 1 && /usr/sbin/sshd -t", + ] + interval: 10s timeout: 5s retries: 3 n8n: - build: - context: ./n8n - args: - N8N_BASE_IMAGE: ${N8N_BASE_IMAGE:-docker.n8n.io/n8nio/n8n:stable} - FFMPEG_IMAGE: ${FFMPEG_IMAGE:-mwader/static-ffmpeg:7.1.1} - POSTIZ_VERSION: ${POSTIZ_NODE_VERSION:-0.2.17} - FFMPEG_STUDIO_VERSION: ${FFMPEG_STUDIO_NODE_VERSION:-1.0.0} - image: syndicator-n8n:stable + build: ./n8n + image: syndicator-n8n:${SYNDICATOR_IMAGE_TAG:-local} restart: unless-stopped ports: - - "${N8N_HOST_PORT:-5678}:5678" - # Password hash written by ./scripts/ensure-n8n-owner.sh (bcrypt; $ escaped as $$). - env_file: - - path: secrets/n8n_owner.env - required: true + - "${N8N_BIND_ADDRESS:-127.0.0.1}:${N8N_HOST_PORT:-5678}:5678" environment: GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-Europe/Zurich} TZ: ${GENERIC_TIMEZONE:-Europe/Zurich} @@ -65,49 +61,83 @@ services: N8N_HOST: ${N8N_HOST:-localhost} N8N_PORT: ${N8N_PORT:-5678} N8N_PROTOCOL: ${N8N_PROTOCOL:-http} - WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:5678/} + N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL:-${WEBHOOK_URL:-http://localhost:5678/}} + N8N_PROXY_HOPS: ${N8N_PROXY_HOPS:-0} N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true" - N8N_RESTRICT_FILE_ACCESS_TO: /files + N8N_RESTRICT_FILE_ACCESS_TO: /files;/syndicator N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE:-false} N8N_COMMUNITY_PACKAGES_ENABLED: "true" + N8N_UNVERIFIED_PACKAGES_ENABLED: "true" + NODE_FUNCTION_ALLOW_BUILTIN: fs,path + N8N_RUNNERS_TASK_TIMEOUT: "300" + N8N_COMPRESSION_NODE_MAX_DECOMPRESSED_SIZE_BYTES: "268435456" + N8N_COMPRESSION_NODE_MAX_ZIP_ENTRIES: "1000" N8N_INSTANCE_OWNER_MANAGED_BY_ENV: "true" N8N_INSTANCE_OWNER_EMAIL: ${N8N_OWNER_EMAIL:?set N8N_OWNER_EMAIL in .env} N8N_INSTANCE_OWNER_FIRST_NAME: ${N8N_OWNER_FIRST_NAME:-Syndicator} N8N_INSTANCE_OWNER_LAST_NAME: ${N8N_OWNER_LAST_NAME:-Owner} + N8N_OWNER_PASSWORD: ${N8N_OWNER_PASSWORD:?set N8N_OWNER_PASSWORD in .env} volumes: - n8n_data:/home/node/.n8n - n8n_files:/files + - sftp_data:/syndicator depends_on: files-init: condition: service_completed_successfully sftp: - condition: service_started - pyautoflip: condition: service_healthy + pyautoflip: + condition: service_started healthcheck: test: [ "CMD-SHELL", - "wget -qO- http://127.0.0.1:5678/healthz >/dev/null || exit 1", + "wget -qO- http://127.0.0.1:5678/healthz/readiness >/dev/null || exit 1", ] interval: 30s timeout: 5s retries: 5 start_period: 60s + n8n-reconcile: + build: ./n8n + image: syndicator-n8n:${SYNDICATOR_IMAGE_TAG:-local} + restart: "no" + profiles: ["reconcile"] + user: "node" + depends_on: + n8n: + condition: service_healthy + environment: + N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env} + N8N_OWNER_EMAIL: ${N8N_OWNER_EMAIL:?set N8N_OWNER_EMAIL in .env} + N8N_OWNER_PASSWORD: ${N8N_OWNER_PASSWORD:?set N8N_OWNER_PASSWORD in .env} + OPENAI_API_KEY: ${OPENAI_API_KEY:?set OPENAI_API_KEY in .env} + POSTIZ_API_KEY: ${POSTIZ_API_KEY:?set POSTIZ_API_KEY in .env} + N8N_INTERNAL_URL: http://n8n:5678 + volumes: + - n8n_data:/home/node/.n8n + - ./n8n/reconcile.js:/opt/syndicator/reconcile.js:ro + - ./n8n/workflows:/opt/syndicator/workflows:ro + - ./n8n/credentials:/opt/syndicator/credentials:ro + entrypoint: ["node", "/opt/syndicator/reconcile.js"] + pyautoflip: - build: ./pyautoflip - image: syndicator-pyautoflip:local + build: + context: ./pyautoflip + args: + PYAUTOFLIP_WARM_MODELS: ${PYAUTOFLIP_WARM_MODELS:-1} + image: syndicator-pyautoflip:${SYNDICATOR_IMAGE_TAG:-local} # Same uid as n8n (node) so /files writes are readable by both. - # HOME must be writable: compose user override otherwise sets HOME=/ and - # InsightFace fails creating /.insightface (EACCES). + # HOME contains the image-pinned InsightFace models and remains writable. user: "1000:1000" environment: HOME: /home/pyautoflip + PYAUTOFLIP_ALLOWED_ROOTS: /files;/syndicator restart: unless-stopped volumes: - n8n_files:/files - - pyautoflip_home:/home/pyautoflip + - sftp_data:/syndicator depends_on: files-init: condition: service_completed_successfully @@ -131,4 +161,3 @@ volumes: sftp_host_keys: n8n_data: n8n_files: - pyautoflip_home: diff --git a/docs/adr/0001-deployment-model.md b/docs/adr/0001-deployment-model.md new file mode 100644 index 0000000..606e471 --- /dev/null +++ b/docs/adr/0001-deployment-model.md @@ -0,0 +1,120 @@ +# ADR 0001: Compose as the application boundary + +Status: accepted +Date: 2026-08-12 + +## Context + +Syndicator runs three materially different services on one machine: + +- n8n/Node with ffmpeg and two community node packages +- pyautoflip/Python with native media and machine-learning dependencies +- a chrooted OpenSSH SFTP endpoint + +The previous setup mixed a small declarative Compose file with a large, +stateful host bootstrap. Image tags floated, workflow IDs were duplicated in +scripts, and bootstrap read n8n's private SQLite schema. This made Docker +appear to be the source of the complexity, although most fragility was in the +imperative lifecycle around it. + +The supported target for the next one to two years is a developer Mac and one +production Linux host. Repeatability, testability, and low operator effort are +more important than eliminating containers. + +## Decision + +Keep Docker Compose as the application packaging and runtime boundary. + +- Dockerfiles own language runtimes, native libraries, ffmpeg, community + packages, and model preparation. +- Compose owns service networking, health, startup dependencies, ports, and + persistent volumes. +- Operators instantiate with Compose. `bin/syndicator` covers verify, export, + and logs; `scripts/init.sh` creates `.env`. +- Runtime inputs are pinned. Dependency changes arrive as reviewable pull + requests and must pass an isolated full-stack test before deployment. +- Application volumes are disposable; identity (`.env` and SFTP keys) is + supplied at instantiate time. Volume backup and rollback are out of scope; + see [ADR 0002](0002-disposable-instances.md). + +Ansible may be added outside this boundary to prepare a Linux host: install +Docker, configure a firewall or reverse proxy, place the repository and +encrypted secrets, and run `docker compose up -d --build` plus +`bin/syndicator verify`. It must not reproduce +the application installation, workflow import, or update logic. + +Terraform is reserved for infrastructure resources such as a VM, DNS records, +firewall rules, and backup storage. It is not used to configure processes or +packages inside the host. + +## Alternatives considered + +### Native shell installer + +Rejected. It would need to reconcile Node/n8n, npm packages, two ffmpeg +installations, Python 3.12 and native ML libraries, model downloads, OpenSSH +users and chroot permissions, systemd units, and macOS/Linux differences. +Making that installer idempotent and reversible would recreate a container +runtime poorly. + +### Native Ansible services + +Viable only if removing Docker becomes a hard operational requirement. +Ansible improves idempotency over shell, but the role would still own all +language runtimes, system packages, users, permissions, and service units. A +Linux VM test matrix would also replace the current Mac/Linux parity. + +### Ansible wrapping Compose + +Compatible with this decision. It becomes worthwhile when rebuilding the +production host itself is frequent or when firewall, TLS, and secret placement +need to be managed. For one host it remains optional so the application does +not acquire a second mandatory control plane. + +### Puppet + +Rejected for the current scale. Its long-lived host convergence model is +valuable for fleets of managed machines, not one application host. + +### Terraform + +Rejected as an application installer. Provisioners or remote-exec would make +host changes less testable and less idempotent. Terraform can still create the +host around Syndicator. + +### Kubernetes or Nomad + +Rejected. The stack is single-host, uses SQLite and local shared storage, and +does not need scheduling or high availability. A cluster orchestrator would +add more state and failure modes than it removes. + +### Direct n8n access to the SFTP data volume + +Accepted. n8n mounts `sftp_data` at `/syndicator` and uses Read/Write File +nodes. The external SFTP interface stays stable for callers. Revisit if +services move to separate machines. + +## Consequences + +- Docker remains a prerequisite, but operators need only the lifecycle + commands documented in [operations.md](../operations.md). +- The SFTP base is amd64-only and runs through Docker Desktop emulation on + Apple Silicon. The production Linux host should preferably be amd64. +- n8n's internal JavaScript task runner is accepted for this trusted, + single-user deployment. External runners should be evaluated before + untrusted users can edit workflows. +- The stack does not provide TLS or webhook authentication. Safe defaults bind + published ports to loopback; exposing them requires an explicit network and + reverse-proxy decision. +- pyautoflip's Python graph and model archive are hash-pinned, but Debian media + packages still come from the live Bookworm repositories. Rebuild from the + same Git revision if a bit-for-bit image recreate is required; use a Debian + snapshot if that recreate must be independent of current Bookworm. + +## Revisit when + +- more than a few hosts need centralized convergence +- n8n moves to PostgreSQL/queue mode or services move to separate machines +- untrusted users can author workflows +- zero-downtime deployment or high availability becomes a requirement +- the external SFTP contract is retired diff --git a/docs/adr/0002-disposable-instances.md b/docs/adr/0002-disposable-instances.md new file mode 100644 index 0000000..f74a4a4 --- /dev/null +++ b/docs/adr/0002-disposable-instances.md @@ -0,0 +1,53 @@ +# ADR 0002: Disposable instances + +Status: accepted +Date: 2026-08-13 + +## Context + +ADR 0001 kept Compose as the application boundary and originally treated +volume backup, restore, and rollback as first-class `bin/syndicator` commands. +That assumed n8n SQLite, SFTP uploads, and host keys were unique state that +had to survive a host loss or a bad update. + +Workflows, credentials, and webhook paths are already reconstructed from git +and `.env` by `scripts/init.sh`, Compose, and in-container reconcile. Uploaded SFTP files can be +re-provided by callers. The remaining identity is `.env`, SFTP host keys, and +authorized client keys — which belong next to other host secrets, not inside +the application lifecycle. + +## Decision + +Instances are disposable. Create them at will from the current checkout. + +- Application volumes (`n8n_data`, `sftp_data`, `n8n_files`) are not backed up + or restored by Syndicator. They may be lost on disaster and on update. +- `.env` and SFTP keys are ingested when an instance is created. Keeping + callers unaware of an update means leaving that identity in place. +- Disaster recovery is a new instance: reprovide `.env`, run `scripts/init.sh` + and `docker compose up -d --build`, then `bin/syndicator verify`. Regenerate + SFTP keys unless they were saved outside Syndicator. Callers re-upload files + and may need to accept a new SSH host key. +- If `.env` and SFTP keys should survive a host loss, back them up outside + this repository. Syndicator does not choose a storage provider or encryption + key lifecycle. + +`bin/syndicator` therefore has no `backup`, `restore`, `update`, `rollback`, +`init`, or `deploy` commands. A software update is `docker compose up -d --build` +on the reviewed revision, followed by `bin/syndicator verify`. + +## Consequences + +- If verification fails, bring the services down; recovery is another + `compose up` plus `verify`, not a volume restore. +- SFTP host keys remain in the `sftp_host_keys` volume so a normal container + recreate does not change the SSH identity. Wiping that volume is visible to + callers. +- Operators who want secret durability use their own backup of `.env` and + `sftp/keys/`, not an application archive. + +## Revisit when + +- callers cannot re-upload staged files +- n8n execution history or unpublished UI edits become source of truth +- zero-downtime updates become a requirement diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..3b1146e --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,237 @@ +# Syndicator operations + +This runbook covers the supported topology: Docker Desktop on a developer Mac +and Docker Engine with the Compose plugin on one Linux host. + +## Prerequisites + +The host needs: + +- Docker Engine or Docker Desktop with `docker compose` +- Bash, Python 3, curl, OpenSSL, and an OpenSSH client +- enough disk for the n8n and pyautoflip images and media staging +- amd64 execution support for the SFTP image; Docker Desktop supplies + emulation on Apple Silicon + +Check the host without changing anything: + +```bash +scripts/doctor.sh +``` + +## First installation + +Create the local configuration: + +```bash +scripts/init.sh +``` + +On the first run this creates `.env`, generates `N8N_ENCRYPTION_KEY`, and stops +with a list of values that still need input. Fill in: + +- n8n owner email and password +- OpenAI API key +- Postiz API key +- the public URL and bind addresses appropriate for the host + +The lifecycle passes `.env` to Compose as an env file, never as shell code. Quote +values that contain spaces or `#`; single quotes preserve `$`, backticks, and +other characters literally. + +Then start the stack and verify: + +```bash +docker compose up -d --build +bin/syndicator verify +``` + +Compose builds and starts the services. `verify` waits for n8n, runs the +in-container reconcile, and checks health, webhook registration, pyautoflip, +and SFTP. Running it again is safe. If source configuration is unchanged and +all workflows remain published, n8n import is skipped. + +Unless `SYNDICATOR_IMAGE_TAG` is set, images are tagged `local`. + +## Local and network configuration + +The checked-in defaults bind n8n and SFTP to `127.0.0.1`. This is safe for +local development but not reachable by another LAN machine. + +For trusted LAN access, set the required bind addresses explicitly: + +```dotenv +N8N_BIND_ADDRESS=0.0.0.0 +SFTP_BIND_ADDRESS=0.0.0.0 +N8N_HOST=192.0.2.10 +N8N_WEBHOOK_URL=http://192.0.2.10:5678/ +``` + +Use the real host address, not the documentation address above. Restrict access +with the host firewall. + +For an internet-facing host: + +- keep `N8N_BIND_ADDRESS=127.0.0.1` +- terminate HTTPS in Caddy, nginx, or another reverse proxy +- set `N8N_WEBHOOK_URL=https://.../`, `N8N_PROTOCOL=https`, and + `N8N_SECURE_COOKIE=true` +- set `N8N_PROXY_HOPS=1` when there is one trusted reverse proxy +- expose SFTP only to required source addresses + +The current webhook workflows do not authenticate requests. Do not publish the +n8n port directly to an untrusted network. Adding webhook authentication is an +application contract change and must be coordinated with callers. + +## Routine commands + +Inspect logs: + +```bash +bin/syndicator logs +``` + +Run reconcile and non-mutating service checks: + +```bash +bin/syndicator verify +``` + +Export workflows after editing them in n8n: + +```bash +bin/syndicator export +python3 -m unittest discover -s tests -p 'test_*.py' +``` + +Exports are normalized: pin data, instance IDs, project ownership, and version +metadata are removed. Review the resulting JSON before committing it. + +If the owner password changes, update `N8N_OWNER_PASSWORD` in `.env`, then +recreate n8n and verify: + +```bash +docker compose up -d --force-recreate n8n +bin/syndicator verify +``` + +After adding or removing a public key under `sftp/keys/`, apply it through the +supported hook: + +```bash +docker compose up -d --force-recreate sftp +``` + +## Dependency updates + +Container, npm, pip, and GitHub Actions dependencies are proposed weekly by +Dependabot. Do not edit a floating `latest` or `stable` tag on the server. + +For an update: + +1. Review the release notes and dependency diff. +2. Let CI validate manifests, audit npm dependencies, build both images, start + an isolated stack twice, and test SFTP I/O. +3. Pull the reviewed Git revision on the server. +4. Run: + +```bash +docker compose up -d --build --pull always +bin/syndicator verify +``` + +This rebuilds images from the current checkout, starts the stack, reconciles +n8n, and verifies. Instance volumes are not snapshotted; callers keep working +when `.env`, SFTP host keys, and authorized client keys stay in place. + +If reconcile or verification fails, bring the new services down. Do not simply +restart the failed containers; fix the checkout and start again. + +## Disaster recovery + +Syndicator does not back up application volumes. A lost host is a new instance: + +1. Install the prerequisites. +2. Check out the desired Git revision. +3. Restore `.env` from wherever you keep secrets, or recreate it and fill the + required values. +4. Run `scripts/init.sh`, `docker compose up -d --build`, and + `bin/syndicator verify`. +5. Restore authorized client public keys under `sftp/keys/` if you kept them. +6. Verify firewall, DNS, and reverse proxy. + +SFTP host keys are generated on first start. Callers must accept the new host +key unless you restore the `sftp_host_keys` volume yourself. Uploaded files are +gone; callers re-upload. If you want `.env` and SFTP keys to survive a host +loss, back them up outside Syndicator. + +## Testing + +Fast checks: + +```bash +python3 -m unittest discover -s tests -p 'test_*.py' +bash tests/test-init.sh +bash tests/validate-compose.sh +npm audit --prefix n8n +``` + +Full isolated test: + +```bash +bash tests/validate-compose.sh build n8n pyautoflip +bash tests/integration/reframe.sh +bash tests/integration/stack.sh +``` + +CI first builds the production model-warmed image and performs a real reframe. +The stack integration test uses random loopback ports and a unique Compose +project. It starts the stack, verifies twice, checks that an unchanged +reconcile is skipped, uploads over SFTP, and removes all test containers and +volumes. A separate Buildx job verifies n8n and pyautoflip for Linux arm64. + +## Troubleshooting + +If n8n is unavailable during reconcile, inspect readiness and logs: + +```bash +docker compose ps +bin/syndicator logs n8n +``` + +The stack uses `/healthz/readiness`, not `/healthz`, so first-start database +migrations must finish before provisioning starts. + +If SFTP host-key verification changes unexpectedly, do not delete the client +known-host entry until the cause is understood. Host keys are persistent state +in `sftp_host_keys` and survive container recreate, but not a volume wipe. + +If credentials cannot be decrypted, `N8N_ENCRYPTION_KEY` in `.env` does not +match the existing `n8n_data` volume. Use the original key, or remove the +volume and let reconcile recreate credentials. + +If Apple Silicon reports an SFTP platform warning, confirm +`SFTP_PLATFORM=linux/amd64`; the image is intentionally emulated. + +The n8n image may log that its internal Python runner is absent. Syndicator +uses JavaScript Code nodes and pyautoflip as a separate Python service, so no +Python n8n runner is required. If untrusted users gain workflow-edit access, +move JavaScript execution to n8n's external runner model instead of adding +Python to the main container. + +## Optional host automation + +Ansible is intentionally not required for application operation. Add it when +the Linux machine itself must be recreated automatically. Its responsibilities +should stop at: + +- installing a reviewed Docker Engine/Compose version and host utilities +- creating the deployment user and directory +- configuring firewall, TLS proxy, and optional off-host secret backup +- placing `.env` and other bootstrap secrets from a vault +- checking out a reviewed Git revision and running `docker compose up -d --build` + plus `bin/syndicator verify` + +Do not duplicate Compose services, Dockerfile package installation, or n8n +bootstrap in Ansible. Terraform belongs one level further out: VM, DNS, +network rules, and storage resources only. diff --git a/n8n/.dockerignore b/n8n/.dockerignore new file mode 100644 index 0000000..226725d --- /dev/null +++ b/n8n/.dockerignore @@ -0,0 +1,9 @@ +* +!Dockerfile +!entrypoint.sh +!reconcile.js +!hash-owner-password.js +!package.json +!package-lock.json +!workflows/*.json +!credentials/*.template.json diff --git a/n8n/Dockerfile b/n8n/Dockerfile index 7bea7d2..9e1b936 100644 --- a/n8n/Dockerfile +++ b/n8n/Dockerfile @@ -1,16 +1,8 @@ -# Custom n8n image: static ffmpeg/ffprobe + community nodes (Postiz, FFmpeg Studio). -# Base tag stays :stable so scripts/update.sh can pull security patches; -# override N8N_BASE_IMAGE to pin a digest/version when needed. +# Custom n8n image: static ffmpeg/ffprobe + locked community nodes. -ARG N8N_BASE_IMAGE=docker.n8n.io/n8nio/n8n:stable -ARG FFMPEG_IMAGE=mwader/static-ffmpeg:7.1.1 +FROM mwader/static-ffmpeg:7.1.1@sha256:11a44711684c0b9f754c047dcd64235b8b52deab251bd0e0a86f22faa160749c AS ffmpeg -FROM ${FFMPEG_IMAGE} AS ffmpeg - -FROM ${N8N_BASE_IMAGE} - -ARG POSTIZ_VERSION=0.2.17 -ARG FFMPEG_STUDIO_VERSION=1.0.0 +FROM docker.io/n8nio/n8n:2.33.7@sha256:3989d9b8ebb77b4ee8f604519eb73e44f4384bfaa689526e0104eed79a237d30 USER root @@ -18,18 +10,21 @@ COPY --from=ffmpeg /ffmpeg /usr/local/bin/ffmpeg COPY --from=ffmpeg /ffprobe /usr/local/bin/ffprobe # Seed community packages outside the data volume. The entrypoint copies them -# into /home/node/.n8n/nodes on first start (volume mount hides image content). -RUN mkdir -p /opt/n8n-nodes-seed \ - && cd /opt/n8n-nodes-seed \ - && npm init -y \ - && npm install --omit=dev \ - "n8n-nodes-postiz@${POSTIZ_VERSION}" \ - "n8n-nodes-ffmpeg-studio@${FFMPEG_STUDIO_VERSION}" \ +# into /home/node/.n8n/nodes when the lock changes. +WORKDIR /opt/n8n-nodes-seed +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev \ && chown -R node:node /opt/n8n-nodes-seed COPY entrypoint.sh /entrypoint-syndicator.sh -RUN chmod +x /entrypoint-syndicator.sh +COPY reconcile.js /opt/syndicator/reconcile.js +COPY hash-owner-password.js /opt/syndicator/hash-owner-password.js +COPY workflows /opt/syndicator/workflows +COPY credentials /opt/syndicator/credentials +RUN chmod +x /entrypoint-syndicator.sh \ + && chown -R node:node /opt/syndicator USER node +WORKDIR /home/node ENTRYPOINT ["tini", "--", "/entrypoint-syndicator.sh"] diff --git a/n8n/credentials/sftp.template.json b/n8n/credentials/sftp.template.json deleted file mode 100644 index ee5afca..0000000 --- a/n8n/credentials/sftp.template.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account", - "type": "sftp", - "data": { - "host": "${SFTP_HOST}", - "username": "${SFTP_USERNAME}", - "privateKey": "${SFTP_PRIVATE_KEY}" - } - } -] diff --git a/n8n/entrypoint.sh b/n8n/entrypoint.sh index 13b2b24..a889234 100755 --- a/n8n/entrypoint.sh +++ b/n8n/entrypoint.sh @@ -1,17 +1,32 @@ #!/bin/sh -# Seed community nodes into the n8n data volume when missing, then start n8n. +# Sync locked community nodes into the n8n data volume, then start n8n. set -eu NODES_DIR="/home/node/.n8n/nodes" SEED_DIR="/opt/n8n-nodes-seed" +SEED_MARKER="$NODES_DIR/.syndicator-seed.sha256" -if [ -d "$SEED_DIR/node_modules" ]; then - mkdir -p "$NODES_DIR" - if [ ! -f "$NODES_DIR/package.json" ]; then - echo "Seeding community nodes into $NODES_DIR" +if [ -d "$SEED_DIR/node_modules" ] && [ -f "$SEED_DIR/package-lock.json" ]; then + seed_hash="$(sha256sum "$SEED_DIR/package-lock.json" | cut -d ' ' -f 1)" + installed_hash="$(cat "$SEED_MARKER" 2>/dev/null || true)" + if [ "$seed_hash" != "$installed_hash" ]; then + echo "Syncing locked community nodes into $NODES_DIR" + rm -rf "$NODES_DIR" + mkdir -p "$NODES_DIR" cp -a "$SEED_DIR"/. "$NODES_DIR"/ + printf '%s\n' "$seed_hash" >"$SEED_MARKER" fi fi +if [ -z "${N8N_INSTANCE_OWNER_PASSWORD_HASH:-}" ]; then + if [ -z "${N8N_OWNER_PASSWORD:-}" ]; then + echo "N8N_OWNER_PASSWORD is required" >&2 + exit 1 + fi + N8N_INSTANCE_OWNER_PASSWORD_HASH="$(node /opt/syndicator/hash-owner-password.js)" + export N8N_INSTANCE_OWNER_PASSWORD_HASH + unset N8N_OWNER_PASSWORD +fi + # Preserve upstream custom-certificate handling + default start. exec /docker-entrypoint.sh "$@" diff --git a/n8n/hash-owner-password.js b/n8n/hash-owner-password.js new file mode 100644 index 0000000..00e380a --- /dev/null +++ b/n8n/hash-owner-password.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node +"use strict"; + +const password = process.env.N8N_OWNER_PASSWORD; +if (!password) { + console.error("N8N_OWNER_PASSWORD is required to hash the instance owner password."); + process.exit(1); +} + +function loadBcrypt() { + const candidates = [ + "bcryptjs", + "bcrypt", + "/usr/local/lib/node_modules/n8n/node_modules/bcryptjs", + "/usr/local/lib/node_modules/n8n/node_modules/bcrypt", + ]; + for (const id of candidates) { + try { + return require(id); + } catch { + // try the next candidate + } + } + throw new Error("Unable to load bcrypt from the n8n image"); +} + +const bcrypt = loadBcrypt(); +process.stdout.write(bcrypt.hashSync(password, 10)); diff --git a/n8n/package-lock.json b/n8n/package-lock.json new file mode 100644 index 0000000..43f55e5 --- /dev/null +++ b/n8n/package-lock.json @@ -0,0 +1,1880 @@ +{ + "name": "syndicator-n8n-nodes", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "syndicator-n8n-nodes", + "dependencies": { + "n8n-nodes-ffmpeg-studio": "1.0.0", + "n8n-nodes-postiz": "0.2.17", + "n8n-workflow": "2.33.2" + } + }, + "node_modules/@apm-js-collab/code-transformer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.18.1.tgz", + "integrity": "sha512-u1Hb6bHjWtkSpiprwVP6YaHC1DTN4RAU3zYkUDUe7WMnJwdyU1pwTL9dFKiSJB9IiLue/EQovmyx6xhU7FFtAQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/estree": "^1.0.8", + "astring": "^1.9.0", + "esquery": "^1.7.0", + "meriyah": "^6.1.4", + "semifies": "^1.0.0", + "source-map": "^0.6.0" + }, + "bin": { + "code-transformer": "cli.js" + } + }, + "node_modules/@apm-js-collab/code-transformer-bundler-plugins": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.4.tgz", + "integrity": "sha512-nAfOeZPSUAQvJa1iFT/5oCrTm5YQhMMrfCNthNnaXHZiOQhu1KGuLoIx7HtbAi3wfwaBYLaICPIeenIaEwcXIg==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.18.1", + "es-module-lexer": "^2.1.0", + "magic-string": "^0.30.21", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@apm-js-collab/tracing-hooks": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.13.0.tgz", + "integrity": "sha512-mTvWz9rnQwx1U3h0XPTHaX7bgfkpipLLTQyjlC2cdhQpQEuoLT0AGzoydeoq2NxfEVv6fWOOETcSbb2nptleyw==", + "license": "Apache-2.0", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.18.0", + "debug": "^4.4.1", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", + "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.8", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.8.tgz", + "integrity": "sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz", + "integrity": "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==", + "license": "MIT" + }, + "node_modules/@n8n/constants": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@n8n/constants/-/constants-0.32.0.tgz", + "integrity": "sha512-ewF/bQL2bjmHU9Dv5PnW8uDpYw5GlFJ0/nuAp+RDK9OJXXm19ErHb9itLyANvNTLGPQv8bb0BMvHh2kpWaa/8g==", + "license": "SEE LICENSE IN LICENSE.md" + }, + "node_modules/@n8n/errors": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.13.0.tgz", + "integrity": "sha512-hUZ+ePKp2BzJL40OK1+wTVefJkKLy+mjiBT6KsejkXi1HboYoqHruFqeQZivm5PHhX3CZsHa9ZJy2LGHZrpKCQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "callsites": "3.1.0" + } + }, + "node_modules/@n8n/expression-runtime": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@n8n/expression-runtime/-/expression-runtime-0.24.0.tgz", + "integrity": "sha512-ATII3IU1yAb29oFdGviJuMuL+31oSvTr31FIdVpASDyfHDGPFAbsAC0t/Gpa/RorGvjvmzu6fXow7XA0Xj5jdQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/errors": "0.13.0", + "@n8n/tournament": "1.9.0", + "isolated-vm": "^6.1.2", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "zod": "3.25.67" + } + }, + "node_modules/@n8n/tournament": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.9.0.tgz", + "integrity": "sha512-XB88QlKuD5QO1ERrfVbb2BC2uJrHqHsbmK5FlV+hY7W6IZO4BQtGCTJmHKgwCm+mKsVwBUz3edO8rUadNX6sxg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" + } + }, + "node_modules/@n8n/utils": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/@n8n/utils/-/utils-1.41.0.tgz", + "integrity": "sha512-wjlYqMYRDI8RGNEEBQXA9Pw5FsgdqP+4tvk6HJ9POB5x9yQyF4PhHt45a2RVz0BAoGwZKuxnunR3T+pE/FbjKQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@n8n/constants": "0.32.0", + "nanoid": "3.3.8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz", + "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.10.0.tgz", + "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz", + "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.220.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.10.0.tgz", + "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.10.0.tgz", + "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.10.0.tgz", + "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@sentry/conventions": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", + "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@sentry/core": { + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.70.0.tgz", + "integrity": "sha512-ozhCTDqg89oB4XmWfAwuHshABpvT7AkRpaPnogopPfMAaI61G1t8EKCJ4W7aum8JSBonlfyjPCyW5oYZFm0KvA==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node": { + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.70.0.tgz", + "integrity": "sha512-SPOOVxmKTVIEtqvOKkQT163e/pOwucjS7OPsCHyRs8sFR4nfBNu0EThplyqnvqd5BWBMTPH6WTBQfo+QWHV+HA==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/instrumentation": "^0.220.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.70.0", + "@sentry/node-core": "10.70.0", + "@sentry/opentelemetry": "10.70.0", + "@sentry/server-utils": "10.70.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.70.0.tgz", + "integrity": "sha512-oPOEVVNxv5WHtckx2i06Wi9FLWyvOg/1DUeX732jZ4iqT2nupINaMH4nF4f4kSvUThFnxkFSRQxwqOxgzMKhKA==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.70.0", + "@sentry/opentelemetry": "10.70.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", + "@opentelemetry/instrumentation": ">=0.57.1 <1", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/core": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-http": { + "optional": true + }, + "@opentelemetry/instrumentation": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + } + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.70.0.tgz", + "integrity": "sha512-UNV/2tqypcUK6FDzerAsFJn1Km/c4VZCYkUZDNbnV5S0cwAq2BYKMo4M5vovaLDBQlxA+Wk9ovbxi5wYjjl9fw==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.70.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" + } + }, + "node_modules/@sentry/server-utils": { + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.70.0.tgz", + "integrity": "sha512-rzegZjMFFgCp3o+N8+XU13rfSvz4B+f8rU0ijBGrQcHdMNyfsFDTu1UTm262JofmrV2u+s+D0u0vFTnqtOGkbA==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.3", + "@apm-js-collab/tracing-hooks": "^0.13.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.70.0", + "meriyah": "^6.1.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/tmp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==", + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/buildcheck": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", + "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima-next": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/esprima-next/-/esprima-next-5.8.4.tgz", + "integrity": "sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/import-in-the-middle": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.3.tgz", + "integrity": "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA==", + "license": "Apache-2.0", + "dependencies": { + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isolated-vm": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-6.2.0.tgz", + "integrity": "sha512-UuSlxSHWt2QuJ5WvBhzlIJx2VVZN/a44SqBbEZFKNdvuSyhOvhmyDo8SQ+njVbhnh/njoL/aW0bUTiFYlpweGQ==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/jsonrepair": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.2.tgz", + "integrity": "sha512-Leuly0nbM4R+S5SVJk3VHfw1oxnlEK9KygdZvfUtEtTawNDyzB4qa1xWTmFt1aeoA7sXZkVTRuIixJ8bAvqVUg==", + "license": "ISC", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, + "node_modules/jssha": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", + "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/meriyah": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz", + "integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==", + "license": "ISC", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/n8n-nodes-ffmpeg-studio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/n8n-nodes-ffmpeg-studio/-/n8n-nodes-ffmpeg-studio-1.0.0.tgz", + "integrity": "sha512-S3hKQZanEFEtqATahuQ0jX7aysgxDiO1llZeajuBVgN1COkZjCRMj8tsnTSrGbDBXdQIoiqo7KPYdqL3ti7Xlg==", + "license": "MIT", + "dependencies": { + "@types/tmp": "^0.2.0", + "axios": "^1.6.0", + "tmp": "^0.2.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "n8n-workflow": "^2.14.0" + } + }, + "node_modules/n8n-nodes-postiz": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/n8n-nodes-postiz/-/n8n-nodes-postiz-0.2.17.tgz", + "integrity": "sha512-+dlEfTLuDGUsaO6aldsw3EYwyFNQmhMwEM1hrP0gSuUtPzvwblOd2kPrwKDqE6GKXZa3EnKcdYDKlBps/M1SrQ==", + "license": "MIT", + "engines": { + "node": ">=20.15" + }, + "peerDependencies": { + "n8n-workflow": "*" + } + }, + "node_modules/n8n-workflow": { + "version": "2.33.2", + "resolved": "https://registry.npmjs.org/n8n-workflow/-/n8n-workflow-2.33.2.tgz", + "integrity": "sha512-dGbWefR1opE5fIXKT0AQKDVxXD0LmLX4r84PGl5cGmYzqJhBS/FucRpm+nVo5EBA/fUop5qpGPGcU9BoQUb0aQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@codemirror/autocomplete": "6.20.0", + "@n8n/errors": "0.13.0", + "@n8n/expression-runtime": "0.24.0", + "@n8n/tournament": "1.9.0", + "@n8n/utils": "1.41.0", + "@sentry/core": "^10.55.0", + "@sentry/node": "^10.55.0", + "ast-types": "0.16.1", + "axios": "1.18.0", + "callsites": "3.1.0", + "esprima-next": "5.8.4", + "form-data": "4.0.6", + "jmespath": "0.16.0", + "js-base64": "3.7.8", + "json-schema": "0.4.0", + "jsonrepair": "3.13.2", + "jssha": "3.3.1", + "lodash": "4.18.1", + "luxon": "3.7.2", + "md5": "2.3.0", + "recast": "0.22.0", + "ssh2": "1.15.0", + "title-case": "3.0.3", + "transliteration": "2.3.5", + "uuid": "11.1.1", + "xml2js": "0.6.2" + }, + "peerDependencies": { + "zod": "3.25.67" + } + }, + "node_modules/n8n-workflow/node_modules/axios": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/nan": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", + "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", + "license": "MIT", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/recast": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.22.0.tgz", + "integrity": "sha512-5AAx+mujtXijsEavc5lWXBPQqrM4+Dl5qNH96N2aNeuJFUzpiiToKPsxQD/zAIJHspz7zz0maX0PCtCTFVlixQ==", + "license": "MIT", + "dependencies": { + "assert": "^2.0.0", + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semifies": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semifies/-/semifies-1.0.0.tgz", + "integrity": "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==", + "license": "Apache-2.0" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.9", + "nan": "^2.18.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/transliteration": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/transliteration/-/transliteration-2.3.5.tgz", + "integrity": "sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==", + "license": "MIT", + "dependencies": { + "yargs": "^17.5.1" + }, + "bin": { + "slugify": "dist/bin/slugify", + "transliterate": "dist/bin/transliterate" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/n8n/package.json b/n8n/package.json new file mode 100644 index 0000000..20142bf --- /dev/null +++ b/n8n/package.json @@ -0,0 +1,12 @@ +{ + "name": "syndicator-n8n-nodes", + "private": true, + "dependencies": { + "n8n-nodes-ffmpeg-studio": "1.0.0", + "n8n-nodes-postiz": "0.2.17", + "n8n-workflow": "2.33.2" + }, + "overrides": { + "nanoid": "3.3.18" + } +} diff --git a/n8n/reconcile.js b/n8n/reconcile.js new file mode 100644 index 0000000..b2af06e --- /dev/null +++ b/n8n/reconcile.js @@ -0,0 +1,456 @@ +#!/usr/bin/env node +"use strict"; + +const crypto = require("crypto"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { spawnSync } = require("child_process"); + +const BUNDLE = process.env.SYNDICATOR_BUNDLE || "/opt/syndicator"; +const STATE_FILE = + process.env.SYNDICATOR_RECONCILE_STATE || + "/home/node/.n8n/.syndicator-reconcile.sha256"; +const N8N_BASE = (process.env.N8N_INTERNAL_URL || "http://n8n:5678").replace( + /\/$/, + "", +); + +function fail(message) { + console.error(message); + process.exit(1); +} + +function runN8n(args) { + const result = spawnSync("n8n", args, { encoding: "utf8" }); + if (result.status !== 0) { + fail(`n8n ${args.join(" ")} failed:\n${result.stderr || result.stdout}`); + } + return result.stdout; +} + +function importOwned(kind, inputPath, userId) { + const withOwner = [`import:${kind}`, `--input=${inputPath}`, `--userId=${userId}`]; + const result = spawnSync("n8n", withOwner, { encoding: "utf8" }); + if (result.status === 0) { + return; + } + const msg = `${result.stderr || ""}${result.stdout || ""}`; + if (!msg.includes("already owned")) { + fail(`n8n ${withOwner.join(" ")} failed:\n${msg}`); + } + runN8n([`import:${kind}`, `--input=${inputPath}`]); +} + +function listBundle(kind, suffix) { + const dir = path.join(BUNDLE, kind); + return fs + .readdirSync(dir) + .filter((name) => name.endsWith(suffix)) + .sort() + .map((name) => path.join(dir, name)); +} + +function fingerprint() { + const digest = crypto.createHash("sha256"); + for (const filePath of [ + ...listBundle("credentials", ".template.json"), + ...listBundle("workflows", ".json"), + ]) { + digest.update(path.relative(BUNDLE, filePath)); + digest.update(fs.readFileSync(filePath)); + } + for (const name of ["N8N_ENCRYPTION_KEY", "OPENAI_API_KEY", "POSTIZ_API_KEY"]) { + digest.update(name); + digest.update(process.env[name] || ""); + } + return digest.digest("hex"); +} + +function workflowFiles() { + const files = listBundle("workflows", ".json"); + const workflows = new Map( + files.map((filePath) => [ + filePath, + JSON.parse(fs.readFileSync(filePath, "utf8")), + ]), + ); + const byId = new Map( + [...workflows].map(([filePath, workflow]) => [workflow.id, filePath]), + ); + + function reference(node) { + const value = node.parameters && node.parameters.workflowId; + if (typeof value === "string") { + return value; + } + if (value && typeof value.value === "string") { + return value.value; + } + return null; + } + + const ordered = []; + const visiting = new Set(); + const visited = new Set(); + + function visit(filePath) { + if (visited.has(filePath)) { + return; + } + if (visiting.has(filePath)) { + fail(`workflow dependency cycle at ${path.basename(filePath)}`); + } + visiting.add(filePath); + const dependencies = new Set(); + for (const node of workflows.get(filePath).nodes || []) { + const dependency = reference(node); + if (dependency) { + dependencies.add(dependency); + } + } + for (const dependency of [...dependencies].sort()) { + if (!byId.has(dependency)) { + fail( + `${path.basename(filePath)} references unknown workflow ${dependency}`, + ); + } + visit(byId.get(dependency)); + } + visiting.delete(filePath); + visited.add(filePath); + ordered.push(filePath); + } + + for (const filePath of files) { + visit(filePath); + } + return ordered; +} + +function renderCredential(templatePath) { + const raw = fs.readFileSync(templatePath, "utf8"); + const rendered = raw.replace(/\$\{([A-Z0-9_]+)\}/g, (_, key) => { + if (process.env[key] === undefined) { + fail(`Missing environment value for template: ${key}`); + } + return JSON.stringify(process.env[key]).slice(1, -1); + }); + JSON.parse(rendered); + return rendered; +} + +function cookieHeader(res) { + const raw = + typeof res.headers.getSetCookie === "function" + ? res.headers.getSetCookie() + : []; + const header = res.headers.get("set-cookie"); + const list = raw.length ? raw : header ? [header] : []; + return list.map((item) => item.split(";")[0]).join("; "); +} + +const BROWSER_ID = crypto.randomUUID(); + +async function request(url, { method = "GET", headers = {}, body, cookie } = {}) { + const res = await fetch(url, { + method, + headers: { + "browser-id": BROWSER_ID, + ...headers, + ...(cookie ? { Cookie: cookie } : {}), + }, + body, + }); + const text = await res.text(); + let json = null; + try { + json = text ? JSON.parse(text) : null; + } catch { + json = null; + } + return { res, text, json, cookie: cookieHeader(res) || cookie || "" }; +} + +async function login() { + const { res, json, text, cookie } = await request(`${N8N_BASE}/rest/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + emailOrLdapLoginId: process.env.N8N_OWNER_EMAIL, + password: process.env.N8N_OWNER_PASSWORD, + }), + }); + if (!res.ok) { + fail(`n8n login failed (HTTP ${res.status}): ${text}`); + } + const data = (json && json.data) || json || {}; + if (!data.id) { + fail(`Login response has no owner id: ${text}`); + } + return { userId: data.id, cookie }; +} + +async function rest(cookie, method, urlPath, body) { + return request(`${N8N_BASE}${urlPath}`, { + method, + headers: body ? { "Content-Type": "application/json" } : {}, + body: body ? JSON.stringify(body) : undefined, + cookie, + }); +} + +function payloadOf(result) { + return (result.json && result.json.data) || result.json || {}; +} + +async function getWorkflow(cookie, id) { + for (const urlPath of [`/rest/workflows/${id}`, `/api/v1/workflows/${id}`]) { + const result = await rest(cookie, "GET", urlPath); + if (result.res.ok) { + return payloadOf(result); + } + } + return null; +} + +async function postFirstOk(cookie, paths, body) { + let last = null; + for (const urlPath of paths) { + const result = await rest(cookie, "POST", urlPath, body); + last = result; + if (result.res.ok) { + return result; + } + } + return last; +} + +function isInactive(result) { + return payloadOf(result).active === false; +} + +async function unpublish(cookie, id) { + const result = await postFirstOk( + cookie, + [ + `/rest/workflows/${id}/deactivate`, + `/rest/workflows/${id}/unpublish`, + `/api/v1/workflows/${id}/deactivate`, + `/api/v1/workflows/${id}/unpublish`, + ], + {}, + ); + if (result && (result.res.ok || isInactive(result))) { + return; + } + const current = await getWorkflow(cookie, id); + if (current && current.active !== true) { + return; + } + fail( + `Failed to unpublish workflow ${id} (HTTP ${result ? result.res.status : "none"}): ${result ? result.text : ""}`, + ); +} + +async function publish(cookie, id) { + // Publish through the running n8n HTTP API so production webhooks register + // in its live router. CLI publish from this sidecar only writes the DB. + await unpublish(cookie, id); + const current = await getWorkflow(cookie, id); + if (!current || !current.versionId) { + fail(`Workflow ${id} has no versionId after import`); + } + const result = await postFirstOk( + cookie, + [ + `/rest/workflows/${id}/activate`, + `/rest/workflows/${id}/publish`, + `/api/v1/workflows/${id}/activate`, + `/api/v1/workflows/${id}/publish`, + ], + { versionId: current.versionId }, + ); + if (result && result.res.ok && payloadOf(result).active === true) { + return; + } + fail( + `Failed to publish workflow ${id} (HTTP ${result ? result.res.status : "none"}): ${result ? result.text : ""}`, + ); +} + +async function publishAll(cookie, files) { + for (const filePath of files) { + await publish(cookie, JSON.parse(fs.readFileSync(filePath, "utf8")).id); + } +} + +function importWorkflow(filePath, userId, tmpDir) { + const workflow = JSON.parse(fs.readFileSync(filePath, "utf8")); + workflow.active = false; + delete workflow.activeVersionId; + const out = path.join(tmpDir, path.basename(filePath)); + fs.writeFileSync(out, `${JSON.stringify(workflow)}\n`); + importOwned("workflow", out, userId); +} + +function webhookPaths() { + const paths = []; + for (const filePath of listBundle("workflows", ".json")) { + const workflow = JSON.parse(fs.readFileSync(filePath, "utf8")); + for (const node of workflow.nodes || []) { + if (node.type !== "n8n-nodes-base.webhook") { + continue; + } + const hook = String((node.parameters && node.parameters.path) || "") + .trim() + .replace(/^\/+/, ""); + if (hook) { + paths.push(hook); + } + } + } + return paths.sort(); +} + +function webhookIsLive(status, message) { + if (status === 405) { + return true; + } + return ( + /not registered for GET/i.test(message) || + /Did you mean to make a POST/i.test(message) + ); +} + +async function webhooksLive() { + for (const hook of webhookPaths()) { + const { res, text, json } = await request(`${N8N_BASE}/webhook/${hook}`, { + method: "GET", + }); + const message = (json && json.message) || text || ""; + if (!webhookIsLive(res.status, message)) { + return { ok: false, hook, status: res.status, message }; + } + } + return { ok: true }; +} + +async function assertWebhooksLive() { + const result = await webhooksLive(); + if (result.ok) { + return; + } + fail( + `Production webhook /webhook/${result.hook} is not registered (HTTP ${result.status}): ${result.message}`, + ); +} + +function same(left, right) { + return JSON.stringify(left) === JSON.stringify(right); +} + +async function workflowMatches(cookie, id, sourcePath) { + let payload = null; + for (const urlPath of [`/rest/workflows/${id}`, `/api/v1/workflows/${id}`]) { + const result = await rest(cookie, "GET", urlPath); + if (result.res.status === 200) { + payload = (result.json && result.json.data) || result.json; + break; + } + } + if (!payload) { + return false; + } + const desired = JSON.parse(fs.readFileSync(sourcePath, "utf8")); + if (payload.active !== true) { + return false; + } + return ["name", "nodes", "connections", "settings", "staticData"].every( + (key) => same(payload[key], desired[key]), + ); +} + +async function allWorkflowsCurrent(cookie, files) { + for (const filePath of files) { + const id = JSON.parse(fs.readFileSync(filePath, "utf8")).id; + if (!(await workflowMatches(cookie, id, filePath))) { + return false; + } + } + return true; +} + +async function main() { + for (const name of [ + "N8N_ENCRYPTION_KEY", + "N8N_OWNER_EMAIL", + "N8N_OWNER_PASSWORD", + "OPENAI_API_KEY", + "POSTIZ_API_KEY", + ]) { + if (!process.env[name]) { + fail(`Missing required environment value: ${name}`); + } + } + + const files = workflowFiles(); + if (!files.length) { + fail(`No workflow exports found under ${BUNDLE}`); + } + + const digest = fingerprint(); + const { userId, cookie } = await login(); + if ( + fs.existsSync(STATE_FILE) && + fs.readFileSync(STATE_FILE, "utf8").trim() === digest && + (await allWorkflowsCurrent(cookie, files)) + ) { + if ((await webhooksLive()).ok) { + console.log("n8n bootstrap is already current."); + return; + } + console.log( + "Workflows are current but production webhooks are not registered; republishing...", + ); + await publishAll(cookie, files); + if (!(await allWorkflowsCurrent(cookie, files))) { + fail("Republished workflows differ from source or are inactive."); + } + await assertWebhooksLive(); + console.log("n8n bootstrap complete."); + return; + } + + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "syndicator-")); + try { + console.log(`Importing credentials for owner ${userId}...`); + for (const templatePath of listBundle("credentials", ".template.json")) { + const out = path.join( + tmp, + path.basename(templatePath, ".template.json") + ".json", + ); + fs.writeFileSync(out, renderCredential(templatePath)); + importOwned("credentials", out, userId); + } + + console.log("Importing and publishing workflows..."); + for (const filePath of files) { + importWorkflow(filePath, userId, tmp); + await publish(cookie, JSON.parse(fs.readFileSync(filePath, "utf8")).id); + } + + if (!(await allWorkflowsCurrent(cookie, files))) { + fail("At least one imported workflow differs from source or is inactive."); + } + await assertWebhooksLive(); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + + fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true }); + fs.writeFileSync(STATE_FILE, `${digest}\n`); + console.log("n8n bootstrap complete."); +} + +main().catch((error) => fail(error.stack || String(error))); diff --git a/n8n/workflows/Adapt Feature Image.json b/n8n/workflows/Adapt Feature Image.json index 65a7e84..a09c233 100644 --- a/n8n/workflows/Adapt Feature Image.json +++ b/n8n/workflows/Adapt Feature Image.json @@ -64,24 +64,19 @@ }, { "parameters": { - "protocol": "sftp", - "path": "={{ '/syndicator/' + $json.slug + '/source/' + $json.header_source }}", - "options": {} + "fileSelector": "={{ '/syndicator/' + $json.slug + '/source/' + $json.header_source }}", + "options": { + "dataPropertyName": "data" + } }, "id": "8f1a0d03-f964-45fb-b5fb-a2ee8670eff0", "name": "Download Feature Source", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 448, 0 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -130,7 +125,7 @@ }, { "parameters": { - "jsCode": "function cropBox(width, height, targetRatio, focus) {\n const fx = focus?.x ?? 0.5;\n const fy = focus?.y ?? 0.5;\n const srcRatio = width / height;\n let cropW, cropH;\n if (srcRatio > targetRatio) { cropH = height; cropW = Math.round(height * targetRatio); }\n else { cropW = width; cropH = Math.round(width / targetRatio); }\n let left = Math.round(fx * width - cropW / 2);\n let top = Math.round(fy * height - cropH / 2);\n left = Math.min(Math.max(left, 0), width - cropW);\n top = Math.min(Math.max(top, 0), height - cropH);\n return { left, top, width: cropW, height: cropH };\n}\nfunction even(n) { return n % 2 === 0 ? n : n - 1; }\nfunction fitWithoutUpscale(cropW, cropH, maxW, maxH) {\n if (cropW <= maxW && cropH <= maxH) return { width: cropW, height: cropH };\n const scale = Math.min(maxW / cropW, maxH / cropH);\n return { width: even(Math.floor(cropW * scale)), height: even(Math.floor(cropH * scale)) };\n}\nfunction parseFocus(raw) {\n try {\n let t = raw?.content ?? raw?.text ?? raw?.[0]?.content?.[0]?.text ?? raw?.output?.[0]?.content?.[0]?.text ?? raw;\n if (typeof t !== 'string') t = JSON.stringify(t);\n const m = t.match(/\\{[\\s\\S]*\\}/);\n if (!m) return { x: 0.5, y: 0.5 };\n const j = JSON.parse(m[0]);\n return { x: Number(j.x) || 0.5, y: Number(j.y) || 0.5 };\n } catch (e) { return { x: 0.5, y: 0.5 }; }\n}\nfunction readSize(info) {\n const width = Number(info?.size?.width || info?.width || info?.data?.width || 0);\n const height = Number(info?.size?.height || info?.height || info?.data?.height || 0);\n if (width > 0 && height > 0) return { width, height };\n const geo = String(info?.Geometry || '');\n const m = geo.match(/^(\\d+)x(\\d+)/);\n if (m) return { width: Number(m[1]), height: Number(m[2]) };\n throw new Error('Feature Image Info missing size: ' + JSON.stringify(Object.keys(info || {})));\n}\nconst trigger = $('Adapt Feature Trigger').first().json;\nconst slug = String(trigger.slug || '');\nconst { width, height } = readSize($('Feature Image Info').first().json);\nconst focus = parseFocus($('Feature Crop Focus').first().json);\nconst jobs = [];\n{\n const box = cropBox(width, height, 1080 / 1350, focus);\n const out = fitWithoutUpscale(box.width, box.height, 1080, 1350);\n jobs.push({ platform: 'instagram', crop: box, out, sftp_path: '/syndicator/' + slug + '/header/instagram.jpg' });\n}\nfor (const platform of ['facebook', 'x']) {\n let outW = width, outH = height;\n const maxEdge = Math.max(width, height);\n if (maxEdge > 2048) {\n const scale = 2048 / maxEdge;\n outW = even(Math.floor(width * scale));\n outH = even(Math.floor(height * scale));\n }\n jobs.push({ platform, crop: { left: 0, top: 0, width, height }, out: { width: outW, height: outH }, sftp_path: '/syndicator/' + slug + '/header/' + platform + '.jpg' });\n}\nreturn jobs.map((j) => ({ json: j }));" + "jsCode": "function cropBox(width, height, targetRatio, focus) {\n const fx = focus?.x ?? 0.5;\n const fy = focus?.y ?? 0.5;\n const srcRatio = width / height;\n let cropW, cropH;\n if (srcRatio > targetRatio) { cropH = height; cropW = Math.round(height * targetRatio); }\n else { cropW = width; cropH = Math.round(width / targetRatio); }\n let left = Math.round(fx * width - cropW / 2);\n let top = Math.round(fy * height - cropH / 2);\n left = Math.min(Math.max(left, 0), width - cropW);\n top = Math.min(Math.max(top, 0), height - cropH);\n return { left, top, width: cropW, height: cropH };\n}\nfunction even(n) { return n % 2 === 0 ? n : n - 1; }\nfunction fitWithoutUpscale(cropW, cropH, maxW, maxH) {\n if (cropW <= maxW && cropH <= maxH) return { width: cropW, height: cropH };\n const scale = Math.min(maxW / cropW, maxH / cropH);\n return { width: even(Math.floor(cropW * scale)), height: even(Math.floor(cropH * scale)) };\n}\nfunction parseFocus(raw) {\n try {\n let t = raw?.content ?? raw?.text ?? raw?.[0]?.content?.[0]?.text ?? raw?.output?.[0]?.content?.[0]?.text ?? raw;\n if (typeof t !== 'string') t = JSON.stringify(t);\n const m = t.match(/\\{[\\s\\S]*\\}/);\n if (!m) return { x: 0.5, y: 0.5 };\n const j = JSON.parse(m[0]);\n return { x: Number(j.x) || 0.5, y: Number(j.y) || 0.5 };\n } catch (e) { return { x: 0.5, y: 0.5 }; }\n}\nfunction readSize(info) {\n const width = Number(info?.size?.width || info?.width || info?.data?.width || 0);\n const height = Number(info?.size?.height || info?.height || info?.data?.height || 0);\n if (width > 0 && height > 0) return { width, height };\n const geo = String(info?.Geometry || '');\n const m = geo.match(/^(\\d+)x(\\d+)/);\n if (m) return { width: Number(m[1]), height: Number(m[2]) };\n throw new Error('Feature Image Info missing size: ' + JSON.stringify(Object.keys(info || {})));\n}\nconst trigger = $('Adapt Feature Trigger').first().json;\nconst slug = String(trigger.slug || '');\nconst { width, height } = readSize($('Feature Image Info').first().json);\nconst focus = parseFocus($('Feature Crop Focus').first().json);\nconst jobs = [];\n{\n const box = cropBox(width, height, 1080 / 1350, focus);\n const out = fitWithoutUpscale(box.width, box.height, 1080, 1350);\n jobs.push({ platform: 'instagram', crop: box, out, sftp_path: '/syndicator/' + slug + '/header/instagram.jpg' });\n}\nfor (const platform of ['facebook', 'x']) {\n let outW = width, outH = height;\n const maxEdge = Math.max(width, height);\n if (maxEdge > 2048) {\n const scale = 2048 / maxEdge;\n outW = even(Math.floor(width * scale));\n outH = even(Math.floor(height * scale));\n }\n jobs.push({ platform, crop: { left: 0, top: 0, width, height }, out: { width: outW, height: outH }, sftp_path: '/syndicator/' + slug + '/header/' + platform + '.jpg' });\n}\nconst fs = require('fs');\nconst path = require('path');\nfor (const job of jobs) {\n fs.mkdirSync(path.dirname(job.sftp_path), { recursive: true });\n}\nreturn jobs.map((j) => ({ json: j }));" }, "id": "4df52cea-42cf-475c-b35a-a41df142a831", "name": "Plan Header Jobs", @@ -192,25 +187,18 @@ }, { "parameters": { - "protocol": "sftp", - "operation": "upload", - "path": "={{ $('Plan Header Jobs').item.json.sftp_path }}", + "operation": "write", + "fileName": "={{ $('Plan Header Jobs').item.json.sftp_path }}", "options": {} }, "id": "a520fc28-03b5-498b-bf77-d82046094c37", "name": "Upload Header", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 1792, 0 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -358,8 +346,7 @@ "staticData": null, "meta": { "aiBuilderAssisted": true, - "builderVariant": "mcp", - "instanceId": "91b62e007400f324d5abda851b7466b395e6ae8249f94b33aa082b387b7fb350" + "builderVariant": "mcp" }, "nodeGroups": [], "pinData": {}, @@ -368,29 +355,5 @@ "versionCounter": 2, "triggerCount": 0, "sourceWorkflowId": null, - "tags": [], - "shared": [ - { - "updatedAt": "2026-08-08T14:54:20.448Z", - "createdAt": "2026-08-08T14:54:20.448Z", - "role": "workflow:owner", - "workflowId": "8NOGn9jgOoV0fw0u", - "projectId": "AuzF5v93vrOQ3QqB", - "project": { - "updatedAt": "2026-08-08T14:53:14.033Z", - "createdAt": "2026-08-08T14:51:44.577Z", - "id": "AuzF5v93vrOQ3QqB", - "name": "Syndicator Owner ", - "type": "personal", - "icon": null, - "description": null, - "customTelemetryTags": [], - "creatorId": "2e0ed5be-e08a-448d-a423-6e34be07b789" - } - } - ], - "versionMetadata": { - "name": null, - "description": null - } + "tags": [] } diff --git a/n8n/workflows/Adapt Hugo Media.json b/n8n/workflows/Adapt Hugo Media.json index ce9dd62..492995c 100644 --- a/n8n/workflows/Adapt Hugo Media.json +++ b/n8n/workflows/Adapt Hugo Media.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-08-08T13:50:29.558Z", + "updatedAt": "2026-08-13T20:12:00.000Z", "createdAt": "2026-08-05T15:02:11.968Z", "id": "OGa6Xa8GxkSmA7Cr", "name": "Adapt Hugo Media", @@ -35,7 +35,7 @@ }, { "parameters": { - "jsCode": "const VIDEO_RE = /\\.(mp4|mov|m4v|webm|avi|mkv|mpeg|mpg)$/i;\nfunction hugoBasename(name) {\n const n = String(name || '');\n if (!n) return n;\n if (VIDEO_RE.test(n)) return n.replace(/\\.[^.]+$/, '') + '.mp4';\n return n;\n}\nfunction asBlocks(value) {\n if (Array.isArray(value)) return value;\n if (typeof value === 'string' && value.trim()) {\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed) ? parsed : [];\n } catch (e) {\n return [];\n }\n }\n return [];\n}\n\n// Prefer $input (what the previous node actually passed). Typed workflowInputs\n// on Execute Workflow Trigger often ignore pinData and arrive as nulls.\nconst raw = $input.first().json || {};\nconst slug = String(raw.slug || '').trim();\nconst blocks = asBlocks(raw.blocks);\nconst header = String(raw.header_source || '').trim();\n\nif (!slug) {\n throw new Error(\n 'Adapt Hugo Media: missing slug. Trigger received empty workflowInputs ' +\n '(slug/blocks/header_source). When testing manually, fill the trigger input ' +\n 'fields — pinData on Execute Workflow Trigger is not applied to typed inputs.'\n );\n}\n\nconst base = '/syndicator';\nconst siteRoot = `${base}/hugo-site/content/posts/${slug}`;\nconst sourceRoot = `${base}/${slug}/source`;\nconst jobs = [];\n\nfor (const b of blocks) {\n if (b?.kind !== 'media' || !b.media) continue;\n const m = b.media;\n const source_filename = String(m.source_filename || '').trim();\n if (!source_filename) continue;\n const bundle_filename = hugoBasename(source_filename);\n const kind = m.kind || (VIDEO_RE.test(source_filename) ? 'video' : 'image');\n jobs.push({\n kind,\n source_sftp_path: `${sourceRoot}/${source_filename}`,\n sftp_path: `${siteRoot}/${bundle_filename}`,\n bundle_filename,\n role: '',\n slug,\n });\n}\n\nif (header) {\n const ext = header.includes('.') ? header.slice(header.lastIndexOf('.')) : '.jpg';\n jobs.push({\n kind: 'image',\n source_sftp_path: `${sourceRoot}/${header}`,\n sftp_path: `${siteRoot}/featured${ext}`,\n bundle_filename: `featured${ext}`,\n role: 'featured',\n slug,\n });\n}\n\nif (!jobs.length) return [{ json: { kind: 'noop', skip: true } }];\nreturn jobs.map((j) => ({ json: j }));" + "jsCode": "const VIDEO_RE = /\\.(mp4|mov|m4v|webm|avi|mkv|mpeg|mpg)$/i;\nfunction hugoBasename(name) {\n const n = String(name || '');\n if (!n) return n;\n if (VIDEO_RE.test(n)) return n.replace(/\\.[^.]+$/, '') + '.mp4';\n return n;\n}\nfunction asBlocks(value) {\n if (Array.isArray(value)) return value;\n if (typeof value === 'string' && value.trim()) {\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed) ? parsed : [];\n } catch (e) {\n return [];\n }\n }\n return [];\n}\n\n// Prefer $input (what the previous node actually passed). Typed workflowInputs\n// on Execute Workflow Trigger often ignore pinData and arrive as nulls.\nconst raw = $input.first().json || {};\nconst slug = String(raw.slug || '').trim();\nconst blocks = asBlocks(raw.blocks);\nconst header = String(raw.header_source || '').trim();\n\nif (!slug) {\n throw new Error(\n 'Adapt Hugo Media: missing slug. Trigger received empty workflowInputs ' +\n '(slug/blocks/header_source). When testing manually, fill the trigger input ' +\n 'fields — pinData on Execute Workflow Trigger is not applied to typed inputs.'\n );\n}\n\nconst base = '/syndicator';\nconst siteRoot = `${base}/hugo-site/content/posts/${slug}`;\nconst sourceRoot = `${base}/${slug}/source`;\nconst jobs = [];\n\nfor (const b of blocks) {\n if (b?.kind !== 'media' || !b.media) continue;\n const m = b.media;\n const source_filename = String(m.source_filename || '').trim();\n if (!source_filename) continue;\n const bundle_filename = hugoBasename(source_filename);\n const kind = m.kind || (VIDEO_RE.test(source_filename) ? 'video' : 'image');\n jobs.push({\n kind,\n source_sftp_path: `${sourceRoot}/${source_filename}`,\n sftp_path: `${siteRoot}/${bundle_filename}`,\n bundle_filename,\n role: '',\n slug,\n });\n}\n\nif (header) {\n const ext = header.includes('.') ? header.slice(header.lastIndexOf('.')) : '.jpg';\n jobs.push({\n kind: 'image',\n source_sftp_path: `${sourceRoot}/${header}`,\n sftp_path: `${siteRoot}/featured${ext}`,\n bundle_filename: `featured${ext}`,\n role: 'featured',\n slug,\n });\n}\n\nif (!jobs.length) return [{ json: { kind: 'noop', skip: true } }];\nconst fs = require('fs');\nconst path = require('path');\nfor (const job of jobs) {\n fs.mkdirSync(path.dirname(job.sftp_path), { recursive: true });\n}\nreturn jobs.map((j) => ({ json: j }));" }, "id": "3c3b635c-616c-4685-bfc6-8ef7ad434874", "name": "Plan Site Media Jobs", @@ -82,64 +82,14 @@ { "parameters": { "mode": "runOnceForEachItem", - "jsCode": "const j = $input.item.json;\nconst root = '/files';\nconst raw = String(j.bundle_filename || 'video');\nconst stem = raw.includes('.') ? raw.slice(0, raw.lastIndexOf('.')) : raw;\nconst safe = stem.split('').map((c) => /[a-zA-Z0-9_]/.test(c) ? c : '_').join('').slice(0, 80) || 'video';\nconst local_path = root + '/syndicator-site-' + safe + '-' + Date.now() + '.mp4';\nreturn { json: Object.assign({}, j, { local_path: local_path }) };" - }, - "id": "a9a3d9c7-c671-49c4-a4bb-b00832c1714a", - "name": "Resolve Video Local", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 672, - 352 - ] - }, - { - "parameters": { - "protocol": "sftp", - "path": "={{ $json.source_sftp_path }}", - "options": {} - }, - "id": "5e0fe57f-c744-4812-a558-6b67707b4161", - "name": "Download Site Video", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, - "position": [ - 896, - 352 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } - }, - { - "parameters": { - "operation": "write", - "fileName": "={{ $('Resolve Video Local').item.json.local_path }}", - "options": {} - }, - "id": "5a46c465-2538-4a04-bc1c-8264e3778df5", - "name": "Write Site Video Local", - "type": "n8n-nodes-base.readWriteFile", - "typeVersion": 1.1, - "position": [ - 1120, - 352 - ] - }, - { - "parameters": { - "mode": "runOnceForEachItem", - "jsCode": "const job = $input.item.json;\nconst local = $('Resolve Video Local').item.json.local_path;\nconst outLocal = local.replace(/\\.mp4$/i, '') + '-hugo.mp4';\n// Resize to fit inside 700×394, preserve aspect, no crop.\nconst customArgs = `-y -i ${local} -vf \"scale=700:394:force_original_aspect_ratio=decrease\" -c:v libx264 -preset veryfast -crf 28 -pix_fmt yuv420p -c:a aac -movflags +faststart ${outLocal}`;\nreturn { json: { ...job, local_path: local, out_local: outLocal, customArgs } };" + "jsCode": "const job = $input.item.json;\nfunction quote(p) {\n return '\"' + String(p).replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"') + '\"';\n}\nconst customArgs = `-y -i ${quote(job.source_sftp_path)} -vf \"scale=700:394:force_original_aspect_ratio=decrease\" -c:v libx264 -preset veryfast -crf 28 -pix_fmt yuv420p -c:a aac -movflags +faststart ${quote(job.sftp_path)}`;\nreturn { json: { ...job, customArgs } };" }, "id": "6596da44-d212-41bd-b3b9-9eefdad0f7e5", "name": "Plan Hugo Resize", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 1344, + 672, 352 ] }, @@ -148,40 +98,17 @@ "operation": "custom", "timeoutSeconds": 900, "customArgs": "={{ $json.customArgs }}", - "customReturnFile": true, - "customOutputPath": "={{ $json.out_local }}" + "customReturnFile": false }, "id": "9a0781d5-b7e9-4bc2-8677-d0477dc6813e", "name": "Adapt Hugo Video", "type": "n8n-nodes-ffmpeg-studio.ffmpegVideo", "typeVersion": 1, "position": [ - 1568, + 896, 352 ] }, - { - "parameters": { - "protocol": "sftp", - "operation": "upload", - "path": "={{ $('Plan Hugo Resize').item.json.sftp_path }}", - "options": {} - }, - "id": "c3c9fec5-d2d5-4985-9c27-fb8c12bafa43", - "name": "Upload Site Video", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, - "position": [ - 1792, - 352 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } - }, { "parameters": { "jsCode": "const t = $('Adapt Hugo Trigger').first().json;\nreturn [{ json: { ok: true, slug: t.slug } }];" @@ -191,53 +118,23 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 2016, + 1120, 176 ], "executeOnce": true }, { "parameters": { - "protocol": "sftp", - "path": "={{ $json.source_sftp_path }}", - "options": {} + "jsCode": "const fs = require('fs');\nreturn $input.all().map((item) => {\n const job = item.json;\n fs.copyFileSync(job.source_sftp_path, job.sftp_path);\n return { json: job };\n});" }, "id": "769a3d75-50d8-4514-b102-b16fdaebd864", - "name": "Download Site Image", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, - "position": [ - 1568, - 32 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } - }, - { - "parameters": { - "protocol": "sftp", - "operation": "upload", - "path": "={{ $json.sftp_path }}", - "options": {} - }, - "id": "02114a24-8540-46cd-a549-b9005598f935", - "name": "Upload Site Image", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "name": "Copy Site Image", + "type": "n8n-nodes-base.code", + "typeVersion": 2, "position": [ - 1792, + 896, 32 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -268,75 +165,64 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 1344, + 672, 112 ] } ], "connections": { - "Plan Site Media Jobs": { + "Adapt Hugo Trigger": { "main": [ [ { - "node": "Is Video?", + "node": "Plan Site Media Jobs", "type": "main", "index": 0 } ] ] }, - "Is Video?": { + "Plan Site Media Jobs": { "main": [ [ { - "node": "Resolve Video Local", - "type": "main", - "index": 0 - } - ], - [ - { - "node": "Is Noop?", + "node": "Is Video?", "type": "main", "index": 0 } ] ] }, - "Resolve Video Local": { + "Is Video?": { "main": [ [ { - "node": "Download Site Video", + "node": "Plan Hugo Resize", "type": "main", "index": 0 } - ] - ] - }, - "Download Site Video": { - "main": [ + ], [ { - "node": "Write Site Video Local", + "node": "Is Noop?", "type": "main", "index": 0 } ] ] }, - "Adapt Hugo Video": { + "Plan Hugo Resize": { "main": [ [ { - "node": "Upload Site Video", + "node": "Adapt Hugo Video", "type": "main", "index": 0 } ] ] }, - "Upload Site Video": { + "Adapt Hugo Video": { "main": [ [ { @@ -347,18 +233,7 @@ ] ] }, - "Download Site Image": { - "main": [ - [ - { - "node": "Upload Site Image", - "type": "main", - "index": 0 - } - ] - ] - }, - "Upload Site Image": { + "Copy Site Image": { "main": [ [ { @@ -369,39 +244,6 @@ ] ] }, - "Write Site Video Local": { - "main": [ - [ - { - "node": "Plan Hugo Resize", - "type": "main", - "index": 0 - } - ] - ] - }, - "Plan Hugo Resize": { - "main": [ - [ - { - "node": "Adapt Hugo Video", - "type": "main", - "index": 0 - } - ] - ] - }, - "Adapt Hugo Trigger": { - "main": [ - [ - { - "node": "Plan Site Media Jobs", - "type": "main", - "index": 0 - } - ] - ] - }, "Is Noop?": { "main": [ [ @@ -413,7 +255,7 @@ ], [ { - "node": "Download Site Image", + "node": "Copy Site Image", "type": "main", "index": 0 } @@ -431,39 +273,14 @@ "staticData": null, "meta": { "aiBuilderAssisted": true, - "builderVariant": "mcp", - "instanceId": "91b62e007400f324d5abda851b7466b395e6ae8249f94b33aa082b387b7fb350" + "builderVariant": "mcp" }, "nodeGroups": [], "pinData": {}, "versionId": "9898ecb7-79c3-4a67-900b-9cc601e01b96", "activeVersionId": "9898ecb7-79c3-4a67-900b-9cc601e01b96", - "versionCounter": 2, + "versionCounter": 3, "triggerCount": 0, "sourceWorkflowId": null, - "tags": [], - "shared": [ - { - "updatedAt": "2026-08-08T14:54:15.797Z", - "createdAt": "2026-08-08T14:54:15.797Z", - "role": "workflow:owner", - "workflowId": "OGa6Xa8GxkSmA7Cr", - "projectId": "AuzF5v93vrOQ3QqB", - "project": { - "updatedAt": "2026-08-08T14:53:14.033Z", - "createdAt": "2026-08-08T14:51:44.577Z", - "id": "AuzF5v93vrOQ3QqB", - "name": "Syndicator Owner ", - "type": "personal", - "icon": null, - "description": null, - "customTelemetryTags": [], - "creatorId": "2e0ed5be-e08a-448d-a423-6e34be07b789" - } - } - ], - "versionMetadata": { - "name": null, - "description": null - } + "tags": [] } diff --git a/n8n/workflows/Adapt Reel Media.json b/n8n/workflows/Adapt Reel Media.json index f499a16..5e859ef 100644 --- a/n8n/workflows/Adapt Reel Media.json +++ b/n8n/workflows/Adapt Reel Media.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-08-09T17:15:22.511Z", + "updatedAt": "2026-08-13T20:05:00.000Z", "createdAt": "2026-08-05T15:02:21.158Z", "id": "y9TTx7N8Iygn88ry", "name": "Adapt Reel Media", @@ -35,7 +35,7 @@ }, { "parameters": { - "jsCode": "const j = $input.first().json;\nconst slug = String(j.slug || 'unknown');\nconst index = Number(j.index || 1);\nconst source_filename = String(j.source_filename || '').trim();\nconst source_sftp_path = '/syndicator/' + slug + '/source/' + source_filename;\nconst base = '/files';\nconst safeSlug = slug.split('').map((c) => /[a-zA-Z0-9_]/.test(c) ? c : '_').join('').slice(0, 60);\nconst stamp = safeSlug + '-' + index + '-' + Date.now();\nconst source_local = base + '/syndicator-source-' + stamp + '.mp4';\nreturn [{ json: {\n slug: slug,\n index: index,\n source_filename: source_filename,\n source_sftp_path: source_sftp_path,\n source_local: source_local,\n video_4x5_local: base + '/syndicator-video-' + stamp + '-4x5.mp4',\n video_9x16_local: base + '/syndicator-video-' + stamp + '-9x16.mp4',\n video_4x5_sftp: '/syndicator/' + slug + '/reels/4x5/' + index + '.mp4',\n video_9x16_sftp: '/syndicator/' + slug + '/reels/9x16/' + index + '.mp4',\n} }];" + "jsCode": "const j = $input.first().json;\nconst slug = String(j.slug || 'unknown');\nconst index = Number(j.index || 1);\nconst source_filename = String(j.source_filename || '').trim();\nconst source_path = '/syndicator/' + slug + '/source/' + source_filename;\nconst video_4x5 = '/syndicator/' + slug + '/reels/4x5/' + index + '.mp4';\nconst video_9x16 = '/syndicator/' + slug + '/reels/9x16/' + index + '.mp4';\nconst fs = require('fs');\nconst path = require('path');\nfs.mkdirSync(path.dirname(video_4x5), { recursive: true });\nfs.mkdirSync(path.dirname(video_9x16), { recursive: true });\nreturn [{ json: {\n slug: slug,\n index: index,\n source_filename: source_filename,\n source_local: source_path,\n video_4x5_local: video_4x5,\n video_9x16_local: video_9x16,\n video_4x5_sftp: video_4x5,\n video_9x16_sftp: video_9x16,\n} }];" }, "id": "46f8dd7d-9ead-45cf-ad9c-a6e0be30a879", "name": "Resolve Paths", @@ -46,114 +46,6 @@ -16 ] }, - { - "parameters": { - "protocol": "sftp", - "path": "={{ $json.source_sftp_path }}", - "options": {} - }, - "id": "1467dfaf-5f3d-453b-bb1d-d906c42bcb5a", - "name": "Download Source", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, - "position": [ - 448, - -16 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } - }, - { - "parameters": { - "operation": "write", - "fileName": "={{ $('Resolve Paths').item.json.source_local }}", - "options": {} - }, - "id": "499b8c19-3f2a-4705-adc8-bf470e9b02ce", - "name": "Write Source Local", - "type": "n8n-nodes-base.readWriteFile", - "typeVersion": 1.1, - "position": [ - 672, - -16 - ] - }, - { - "parameters": { - "protocol": "sftp", - "operation": "upload", - "path": "={{ $('Plan Reframe').item.json.video_4x5_sftp }}", - "options": {} - }, - "id": "f199b77d-8e6d-4ba8-b837-28b255e24536", - "name": "Upload Reel", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, - "position": [ - 1568, - -112 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } - }, - { - "parameters": { - "jsCode": "const trigger = $('Adapt Reel Trigger').first().json;\nconst plan = $('Plan Reframe').first().json;\nreturn [{\n json: {\n slug: trigger.slug,\n index: trigger.index,\n source_filename: trigger.source_filename,\n video_4x5_sftp: plan.video_4x5_sftp,\n video_4x5_local: plan.video_4x5_local,\n video_9x16_sftp: plan.video_9x16_sftp,\n video_9x16_local: plan.video_9x16_local,\n },\n}];" - }, - "id": "1f70fba6-eebb-40ea-ba42-813c951b3bf4", - "name": "Build Result", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 2016, - -16 - ] - }, - { - "parameters": { - "protocol": "sftp", - "operation": "upload", - "path": "={{ $('Plan Reframe').item.json.video_9x16_sftp }}", - "options": {} - }, - "id": "0ed402a8-830f-420e-bb82-3183564bb162", - "name": "Upload Reel 9:16", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, - "position": [ - 1568, - 80 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } - }, - { - "parameters": { - "mode": "combine", - "combineBy": "combineByPosition", - "options": {} - }, - "id": "90ae4bda-f534-4b1a-a73b-6ea1a3c0d80e", - "name": "Merge Variants", - "type": "n8n-nodes-base.merge", - "typeVersion": 3, - "position": [ - 1792, - -16 - ] - }, { "parameters": { "jsCode": "const p = $('Resolve Paths').first().json;\nreturn [{ json: {\n ...p,\n method: 'saliency',\n motion_threshold: 0.5,\n padding_method: 'blur',\n} }];" @@ -163,7 +55,7 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 896, + 448, -16 ] }, @@ -183,7 +75,7 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 1120, + 672, -112 ] }, @@ -203,40 +95,36 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 1120, + 672, 80 ] }, { "parameters": { - "fileSelector": "={{ $json.output_path }}", - "options": { - "dataPropertyName": "data" - } + "mode": "combine", + "combineBy": "combineByPosition", + "options": {} }, - "id": "db04f570-f012-44d9-aa13-6c3484e55a9f", - "name": "Read Reel 4:5", - "type": "n8n-nodes-base.readWriteFile", - "typeVersion": 1.1, + "id": "90ae4bda-f534-4b1a-a73b-6ea1a3c0d80e", + "name": "Merge Variants", + "type": "n8n-nodes-base.merge", + "typeVersion": 3, "position": [ - 1344, - -112 + 896, + -16 ] }, { "parameters": { - "fileSelector": "={{ $json.output_path }}", - "options": { - "dataPropertyName": "data" - } + "jsCode": "const trigger = $('Adapt Reel Trigger').first().json;\nconst plan = $('Plan Reframe').first().json;\nreturn [{\n json: {\n slug: trigger.slug,\n index: trigger.index,\n source_filename: trigger.source_filename,\n video_4x5_sftp: plan.video_4x5_sftp,\n video_4x5_local: plan.video_4x5_local,\n video_9x16_sftp: plan.video_9x16_sftp,\n video_9x16_local: plan.video_9x16_local,\n },\n}];" }, - "id": "8e5b2a15-8a7a-408d-8c67-3e22367afc79", - "name": "Read Reel 9:16", - "type": "n8n-nodes-base.readWriteFile", - "typeVersion": 1.1, + "id": "1f70fba6-eebb-40ea-ba42-813c951b3bf4", + "name": "Build Result", + "type": "n8n-nodes-base.code", + "typeVersion": 2, "position": [ - 1344, - 80 + 1120, + -16 ] } ], @@ -253,61 +141,6 @@ ] }, "Resolve Paths": { - "main": [ - [ - { - "node": "Download Source", - "type": "main", - "index": 0 - } - ] - ] - }, - "Download Source": { - "main": [ - [ - { - "node": "Write Source Local", - "type": "main", - "index": 0 - } - ] - ] - }, - "Upload Reel": { - "main": [ - [ - { - "node": "Merge Variants", - "type": "main", - "index": 0 - } - ] - ] - }, - "Upload Reel 9:16": { - "main": [ - [ - { - "node": "Merge Variants", - "type": "main", - "index": 1 - } - ] - ] - }, - "Merge Variants": { - "main": [ - [ - { - "node": "Build Result", - "type": "main", - "index": 0 - } - ] - ] - }, - "Write Source Local": { "main": [ [ { @@ -338,7 +171,7 @@ "main": [ [ { - "node": "Read Reel 4:5", + "node": "Merge Variants", "type": "main", "index": 0 } @@ -349,29 +182,18 @@ "main": [ [ { - "node": "Read Reel 9:16", - "type": "main", - "index": 0 - } - ] - ] - }, - "Read Reel 4:5": { - "main": [ - [ - { - "node": "Upload Reel", + "node": "Merge Variants", "type": "main", - "index": 0 + "index": 1 } ] ] }, - "Read Reel 9:16": { + "Merge Variants": { "main": [ [ { - "node": "Upload Reel 9:16", + "node": "Build Result", "type": "main", "index": 0 } @@ -389,39 +211,14 @@ "staticData": null, "meta": { "aiBuilderAssisted": true, - "builderVariant": "mcp", - "instanceId": "91b62e007400f324d5abda851b7466b395e6ae8249f94b33aa082b387b7fb350" + "builderVariant": "mcp" }, "nodeGroups": [], "pinData": {}, "versionId": "3fa76090-065a-4a39-858d-14bdcfb278bf", "activeVersionId": "3fa76090-065a-4a39-858d-14bdcfb278bf", - "versionCounter": 4, + "versionCounter": 5, "triggerCount": 0, "sourceWorkflowId": null, - "tags": [], - "shared": [ - { - "updatedAt": "2026-08-08T14:54:25.189Z", - "createdAt": "2026-08-08T14:54:25.189Z", - "role": "workflow:owner", - "workflowId": "y9TTx7N8Iygn88ry", - "projectId": "AuzF5v93vrOQ3QqB", - "project": { - "updatedAt": "2026-08-08T14:53:14.033Z", - "createdAt": "2026-08-08T14:51:44.577Z", - "id": "AuzF5v93vrOQ3QqB", - "name": "Syndicator Owner ", - "type": "personal", - "icon": null, - "description": null, - "customTelemetryTags": [], - "creatorId": "2e0ed5be-e08a-448d-a423-6e34be07b789" - } - } - ], - "versionMetadata": { - "name": "Version 3fa76090", - "description": "" - } + "tags": [] } diff --git a/n8n/workflows/Blog Post Publish.json b/n8n/workflows/Blog Post Publish.json index 93eaec9..eda28e6 100644 --- a/n8n/workflows/Blog Post Publish.json +++ b/n8n/workflows/Blog Post Publish.json @@ -117,24 +117,19 @@ }, { "parameters": { - "protocol": "sftp", - "path": "={{ $('Adapt Feature Image').item.json.header.facebook }}", - "options": {} + "fileSelector": "={{ $('Adapt Feature Image').item.json.header.facebook }}", + "options": { + "dataPropertyName": "data" + } }, "id": "7ca330c5-d1f5-4bc2-a93f-8385682fbf6f", "name": "Download Header FB", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 704, 192 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -213,24 +208,19 @@ }, { "parameters": { - "protocol": "sftp", - "path": "={{ $('Adapt Feature Image').item.json.header.instagram }}", - "options": {} + "fileSelector": "={{ $('Adapt Feature Image').item.json.header.instagram }}", + "options": { + "dataPropertyName": "data" + } }, "id": "6c6bd7a8-85ac-4642-a0ed-b7651e667aa3", "name": "Download Header IG", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 704, 384 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -309,24 +299,19 @@ }, { "parameters": { - "protocol": "sftp", - "path": "={{ $('Adapt Feature Image').item.json.header.x }}", - "options": {} + "fileSelector": "={{ $('Adapt Feature Image').item.json.header.x }}", + "options": { + "dataPropertyName": "data" + } }, "id": "654e05e6-b06e-462c-9f82-e2f9e0428fa7", "name": "Download Header X", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 704, 576 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -602,7 +587,7 @@ }, { "parameters": { - "jsCode": "const root = '/syndicator/hugo-site';\nreturn $('Generate Hugo Index MDs').first().json.indexFiles.map((f) => ({\n json: {\n sftp_path: `${root}/${f.repo_path}`,\n content: f.content,\n },\n}));" + "jsCode": "const fs = require('fs');\nconst path = require('path');\nconst root = '/syndicator/hugo-site';\nreturn $('Generate Hugo Index MDs').first().json.indexFiles.map((f) => {\n const sftp_path = `${root}/${f.repo_path}`;\n fs.mkdirSync(path.dirname(sftp_path), { recursive: true });\n const content = String(f.content ?? '');\n return {\n json: { sftp_path, content },\n binary: {\n data: {\n data: Buffer.from(content, 'utf8').toString('base64'),\n mimeType: 'text/markdown',\n fileName: path.basename(sftp_path),\n },\n },\n };\n});" }, "id": "1301589f-ac65-4019-985e-ebe2768c5c53", "name": "Stage Site Files", @@ -615,27 +600,18 @@ }, { "parameters": { - "protocol": "sftp", - "operation": "upload", - "path": "={{ $json.sftp_path }}", - "binaryData": false, - "fileContent": "={{ $json.content }}", + "operation": "write", + "fileName": "={{ $json.sftp_path }}", "options": {} }, "id": "9429c516-3ce2-4936-8879-3bd6d59746f7", "name": "Upload Site Files", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 704, 0 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -1129,224 +1105,14 @@ "meta": { "templateCredsSetupCompleted": true, "aiBuilderAssisted": true, - "builderVariant": "mcp", - "instanceId": "91b62e007400f324d5abda851b7466b395e6ae8249f94b33aa082b387b7fb350" + "builderVariant": "mcp" }, "nodeGroups": [], - "pinData": { - "Publish Webhook": [ - { - "json": { - "headers": { - "host": "192.168.0.26:5678", - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "user-agent": "python-httpx/0.28.1", - "content-length": "4477", - "content-type": "application/json" - }, - "params": {}, - "query": {}, - "body": { - "slug": "2026-06-03_Athen", - "meta": { - "title": "Athen", - "date": "2026-06-03", - "language": "german", - "lang_code": "de", - "author": "Benno", - "summary": "Von Lefkada aus fahren wir quer durch Griechenland, welches sehr viel grösser ist, als ich mir das vorgestellt habe.", - "position": "37.90973, 23.71095" - }, - "post_url": "https://www.sailingnomads.ch/de/posts/2026-06-03_athen/", - "blocks": [ - { - "kind": "text", - "raw": "Von Lefkada aus fahren wir quer durch Griechenland, welches sehr viel grösser ist, als ich mir das vorgestellt habe." - }, - { - "kind": "title", - "raw": "### Autobahn", - "heading_level": 3 - }, - { - "kind": "media", - "media": { - "kind": "video", - "source_filename": "bruecke_1786291723616_0.mp4", - "alt": "bruecke.mp4" - } - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG_20260527_162635300_HDR.jpg", - "alt": "IMG_20260527_162635300_HDR" - } - }, - { - "kind": "text", - "raw": "Wir fahren an einem Tag bis Athen, was kein Problem ist, denn die griechischen Autobahnen sind unglaublich angenehm. Kaum Verkehr und in top Zustand. Unterwegs machen wir noch kurz Halt beim Korinth Kanal, der wirklich spektakulär ist. Irgendwann müssen wir hier mal durchsegeln." - }, - { - "kind": "title", - "raw": "### Öffentlicher Verkehr", - "heading_level": 3 - }, - { - "kind": "youtube", - "media": { - "kind": "youtube", - "youtube_id": "FAIZtHHsbSM" - } - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG_20260530_083835605_HDR.jpg", - "alt": "IMG_20260530_083835605_HDR" - } - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG_20260529_100817658_HDR.jpg", - "alt": "IMG_20260529_100817658_HDR" - } - }, - { - "kind": "text", - "raw": "Dem Auto scheint in Griechenland alles untergeordnet zu sein. Eine Tatsache, die mir in Athen echt auf die Nerven geht. Gerade mit dem Hund ist das super anstrengend, es wird null Rücksicht genommen und alles ist zugeparkt, sodass man teils auf die Strasse ausweichen muss. Die Athener selbst finden es auch zum Kotzen. Fairerweise muss man sagen, dass sie seit Jahren versuchen, die Fehler der Vergangenheit zu korrigieren. Ein Problem vieler Städte, wo aus reiner Ideologie auf das falsche Verkehrsmittel gesetzt wurde. Ich muss natürlich den neuen ÖV testen. Charly ist weniger begeistert als ich, denn es herrscht Maulkorbpflicht." - }, - { - "kind": "title", - "raw": "### Alexandros", - "heading_level": 3 - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG_20260529_080135001.jpg", - "alt": "IMG_20260529_080135001" - } - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG-20260529-WA0017.jpg", - "alt": "IMG-20260529-WA0017" - } - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG-20260529-WA0014.jpg", - "alt": "IMG-20260529-WA0014" - } - }, - { - "kind": "media", - "media": { - "kind": "video", - "source_filename": "ALEXANDRA_ALONGSIDE_1786294202993_0.mp4", - "alt": "ALEXANDRA ALONGSIDE.mp4" - } - }, - { - "kind": "media", - "media": { - "kind": "video", - "source_filename": "LINE_PRACTICE_1786293568316_0.mp4", - "alt": "LINE PRACTICE.mp4" - } - }, - { - "kind": "text", - "raw": "Der eigentliche Grund, warum wir in Athen sind, ist Alexandros, a.k.a. the epic navigator. Alexandros kenne ich von YouTube, wo er einen Kanal hat und Segeltips gibt. Er hat auch eine Segelschule in Athen und bietet ein 2-tägiges Katamarantraining an. Zufälligerweise haben wir gesehen, dass er noch einen Platz frei hat, und wir haben das kurzerhand gebucht für Alexandra. Alexandra ist dann auch sehr angetan von Kurs und Alexandros konnte ihr viel beibringen. Er geht die Sache wohl sehr viel pragmatischer an als unser deutsche Kollegen, und das scheint besser zu funktionieren für Alexandra." - }, - { - "kind": "title", - "raw": "### Der Denker", - "heading_level": 3 - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG_20260529_114044078.jpg", - "alt": "IMG_20260529_114044078" - } - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG_20260529_115656929.jpg", - "alt": "IMG_20260529_115656929" - } - }, - { - "kind": "media", - "media": { - "kind": "image", - "source_filename": "IMG_20260529_111236563.jpg", - "alt": "IMG_20260529_111236563" - } - }, - { - "kind": "text", - "raw": "Während Alex an ihrem Segelkurs teilnimmt, gehen Charly und ich alte Steine anschauen. Wir fahren mit dem Tram zur Akropolis, wo Charly natürlich nicht rein darf. Hundebisi und 3000 Jahre alter Sandstein sind keine gute Kombination. Wir besteigen den Hügel vis-à-vis, wo es alte Steine gratis und ohne Touristentrubel zu bestaunen gibt. Es ist schon beeindruckend auf Strassen zu wandeln, auf der vor 2500 Jahren wohl auch Sokrates, einer der grössten Denker der Menschheit, seine Abendspaziergänge absolviert hat." - } - ], - "header_source": "header.jpg", - "flags": { - "redeploy": false - } - }, - "webhookUrl": "http://192.168.0.26:5678/webhook/publish", - "executionMode": "production" - }, - "pairedItem": { - "item": 0 - } - } - ] - }, + "pinData": {}, "versionId": "daef5e42-f8d5-45c2-afa6-449ea33388d5", "activeVersionId": "daef5e42-f8d5-45c2-afa6-449ea33388d5", "versionCounter": 29, "triggerCount": 1, "sourceWorkflowId": null, - "tags": [], - "shared": [ - { - "updatedAt": "2026-08-08T14:54:29.929Z", - "createdAt": "2026-08-08T14:54:29.929Z", - "role": "workflow:owner", - "workflowId": "l7HCCWtO1ALC82n6", - "projectId": "AuzF5v93vrOQ3QqB", - "project": { - "updatedAt": "2026-08-08T14:53:14.033Z", - "createdAt": "2026-08-08T14:51:44.577Z", - "id": "AuzF5v93vrOQ3QqB", - "name": "Syndicator Owner ", - "type": "personal", - "icon": null, - "description": null, - "customTelemetryTags": [], - "creatorId": "2e0ed5be-e08a-448d-a423-6e34be07b789" - } - } - ], - "versionMetadata": { - "name": "Version daef5e42", - "description": "" - } + "tags": [] } diff --git a/n8n/workflows/Reel Publish.json b/n8n/workflows/Reel Publish.json index cb175ab..0f01d1e 100644 --- a/n8n/workflows/Reel Publish.json +++ b/n8n/workflows/Reel Publish.json @@ -71,24 +71,19 @@ }, { "parameters": { - "protocol": "sftp", - "path": "={{ $('Merge Adapt Into Payload').item.json.video_4x5_sftp }}", - "options": {} + "fileSelector": "={{ $('Merge Adapt Into Payload').item.json.video_4x5_sftp }}", + "options": { + "dataPropertyName": "data" + } }, "id": "ea4dc89d-cdd9-4830-8dfe-e726a1e99c83", "name": "Download Reel FB", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 672, 160 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -201,24 +196,19 @@ }, { "parameters": { - "protocol": "sftp", - "path": "={{ $('Merge Adapt Into Payload').item.json.video_4x5_sftp }}", - "options": {} + "fileSelector": "={{ $('Merge Adapt Into Payload').item.json.video_4x5_sftp }}", + "options": { + "dataPropertyName": "data" + } }, "id": "6a71a2d9-aa28-46ad-afa9-44e0071172c5", "name": "Download Reel IG", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 672, -32 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -332,24 +322,19 @@ }, { "parameters": { - "protocol": "sftp", - "path": "={{ $('Merge Adapt Into Payload').item.json.video_4x5_sftp }}", - "options": {} + "fileSelector": "={{ $('Merge Adapt Into Payload').item.json.video_4x5_sftp }}", + "options": { + "dataPropertyName": "data" + } }, "id": "7f8e4be0-8c5b-4e1e-93e2-784c7f02aedb", "name": "Download Reel X", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 0, 576 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -661,24 +646,19 @@ }, { "parameters": { - "protocol": "sftp", - "path": "={{ $('Merge Adapt Into Payload').item.json.video_9x16_sftp }}", - "options": {} + "fileSelector": "={{ $('Merge Adapt Into Payload').item.json.video_9x16_sftp }}", + "options": { + "dataPropertyName": "data" + } }, "id": "552b087a-94f1-40c8-858b-076f00db6a17", "name": "Download Reel YT", - "type": "n8n-nodes-base.ftp", - "typeVersion": 1, + "type": "n8n-nodes-base.readWriteFile", + "typeVersion": 1.1, "position": [ 1568, -224 - ], - "credentials": { - "sftp": { - "id": "FBrOT9WTXTtyPdxi", - "name": "FTP account" - } - } + ] }, { "parameters": { @@ -1084,8 +1064,7 @@ "meta": { "templateCredsSetupCompleted": true, "aiBuilderAssisted": true, - "builderVariant": "mcp", - "instanceId": "91b62e007400f324d5abda851b7466b395e6ae8249f94b33aa082b387b7fb350" + "builderVariant": "mcp" }, "nodeGroups": [], "pinData": {}, @@ -1094,29 +1073,5 @@ "versionCounter": 20, "triggerCount": 1, "sourceWorkflowId": null, - "tags": [], - "shared": [ - { - "updatedAt": "2026-08-08T14:54:34.710Z", - "createdAt": "2026-08-08T14:54:34.710Z", - "role": "workflow:owner", - "workflowId": "zh21miLsQC8Jvua6", - "projectId": "AuzF5v93vrOQ3QqB", - "project": { - "updatedAt": "2026-08-08T14:53:14.033Z", - "createdAt": "2026-08-08T14:51:44.577Z", - "id": "AuzF5v93vrOQ3QqB", - "name": "Syndicator Owner ", - "type": "personal", - "icon": null, - "description": null, - "customTelemetryTags": [], - "creatorId": "2e0ed5be-e08a-448d-a423-6e34be07b789" - } - } - ], - "versionMetadata": { - "name": "Version be986d7e", - "description": "" - } + "tags": [] } diff --git a/pyautoflip/.dockerignore b/pyautoflip/.dockerignore new file mode 100644 index 0000000..80e2f97 --- /dev/null +++ b/pyautoflip/.dockerignore @@ -0,0 +1,5 @@ +* +!Dockerfile +!app.py +!requirements.txt +!warm_models.py diff --git a/pyautoflip/Dockerfile b/pyautoflip/Dockerfile index 5a0db1c..0d8f190 100644 --- a/pyautoflip/Dockerfile +++ b/pyautoflip/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-slim-bookworm +FROM python:3.12-slim-bookworm@sha256:4766d8b510c428e595d74b9cc5bbb2fae8e26316fffb4adc89908d79aacd58a2 RUN apt-get update \ && apt-get install -y --no-install-recommends \ @@ -12,7 +12,7 @@ RUN apt-get update \ WORKDIR /app COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt \ +RUN pip install --no-cache-dir --require-hashes -r requirements.txt \ && chown -R pyautoflip:pyautoflip /app # Match n8n's node user (uid 1000). Compose may also set user: "1000:1000"; @@ -22,15 +22,12 @@ ENV PYAUTOFLIP_FILES_ROOT=/files USER pyautoflip -# Warm InsightFace packs into $HOME/.insightface so runtime needs no downloads. -# pyautoflip's face detector defaults to buffalo_s (not buffalo_l). -RUN python - <<'PY' -from insightface.app import FaceAnalysis -for name in ("buffalo_s", "buffalo_l"): - app = FaceAnalysis(name=name, providers=["CPUExecutionProvider"]) - app.prepare(ctx_id=-1, det_size=(640, 640)) - print(f"insightface {name} ready") -PY +# Install and verify pyautoflip's buffalo_s model in the immutable image. +ARG PYAUTOFLIP_WARM_MODELS=1 +COPY --chown=pyautoflip:pyautoflip warm_models.py . +RUN if [ "$PYAUTOFLIP_WARM_MODELS" = "1" ]; then \ + python warm_models.py; \ + fi COPY --chown=pyautoflip:pyautoflip app.py . diff --git a/pyautoflip/README.md b/pyautoflip/README.md index 362a23f..fff476c 100644 --- a/pyautoflip/README.md +++ b/pyautoflip/README.md @@ -2,12 +2,15 @@ HTTP wrapper around [pyautoflip](https://github.com/AhmedHisham1/pyautoflip) (saliency mode) for the n8n **Adapt Reel Media** workflow. Runs in its own container next to n8n and shares -the `/files` volume so videos are not uploaded through HTTP bodies. +`/files` plus the SFTP tree at `/syndicator`, so reframes read and write on disk +instead of going through n8n binary. Defined as the `pyautoflip` service in [`../docker-compose.yml`](../docker-compose.yml). ```bash -docker compose up -d --build pyautoflip +# From the repository root: +docker compose up -d --build +bin/syndicator verify ``` ## API @@ -20,8 +23,8 @@ docker compose up -d --build pyautoflip ```json POST /reframe { - "input_path": "/files/syndicator-source-….mp4", - "output_path": "/files/syndicator-video-…-9x16.mp4", + "input_path": "/syndicator//source/clip.mp4", + "output_path": "/syndicator//reels/9x16/1.mp4", "aspect_ratio": "9:16", "method": "saliency", "motion_threshold": 0.5, @@ -29,7 +32,7 @@ POST /reframe } ``` -Paths must stay under `/files` (or `$PYAUTOFLIP_FILES_ROOT`). Response includes +Paths must stay under `/files` or `/syndicator` (`$PYAUTOFLIP_ALLOWED_ROOTS`). Response includes `output_path`, `width`, `height`, and `duration_ms`. **Upstream workarounds** (applied in `app.py` before `reframe_video`): @@ -49,6 +52,9 @@ Encode knobs (optional env on the `pyautoflip` service): ## Local image build +The build downloads the InsightFace `buffalo_s` archive, verifies its pinned +SHA-256 checksum, and stores the extracted model in the image. + ```bash cd pyautoflip docker build -t syndicator-pyautoflip:local . @@ -57,7 +63,4 @@ docker build -t syndicator-pyautoflip:local . ## n8n workflow **Adapt Reel Media** (`y9TTx7N8Iygn88ry`) calls this service. The live workflow -is edited in n8n; export back with `scripts/export-workflows.sh`. - -After cutover, remove any ad-hoc `pyautoflip` service from host `~/n8n/compose.yaml` -so `docker-compose.yml` is the single definition. +is edited in n8n; export back with `bin/syndicator export`. diff --git a/pyautoflip/app.py b/pyautoflip/app.py index cdac7b2..6fd2d6e 100644 --- a/pyautoflip/app.py +++ b/pyautoflip/app.py @@ -17,6 +17,13 @@ from pydantic import BaseModel, Field FILES_ROOT = Path(os.environ.get("PYAUTOFLIP_FILES_ROOT", "/files")).resolve() +ALLOWED_ROOTS = tuple( + Path(part.strip()).resolve() + for part in os.environ.get( + "PYAUTOFLIP_ALLOWED_ROOTS", str(FILES_ROOT) + ).split(";") + if part.strip() +) # Final encode quality (libx264). Lower = better quality / larger files. ENCODE_CRF = int(os.environ.get("PYAUTOFLIP_CRF", "18")) ENCODE_PRESET = os.environ.get("PYAUTOFLIP_PRESET", "medium") @@ -44,14 +51,17 @@ class ReframeResponse(BaseModel): def _resolve_under_files(raw: str) -> Path: path = Path(raw).resolve() - try: - path.relative_to(FILES_ROOT) - except ValueError as exc: - raise HTTPException( - status_code=400, - detail=f"path must be under {FILES_ROOT}: {raw}", - ) from exc - return path + for root in ALLOWED_ROOTS: + try: + path.relative_to(root) + return path + except ValueError: + continue + allowed = ";".join(str(root) for root in ALLOWED_ROOTS) + raise HTTPException( + status_code=400, + detail=f"path must be under {allowed}: {raw}", + ) def _make_even(n: int) -> int: diff --git a/pyautoflip/requirements.in b/pyautoflip/requirements.in new file mode 100644 index 0000000..13cd84a --- /dev/null +++ b/pyautoflip/requirements.in @@ -0,0 +1,6 @@ +pyautoflip==0.2.1 +fastapi==0.115.12 +uvicorn[standard]==0.34.2 +pydantic==2.11.3 +torch==2.13.0+cpu +torchvision==0.28.0+cpu diff --git a/pyautoflip/requirements.txt b/pyautoflip/requirements.txt index 77f2496..0c2baf5 100644 --- a/pyautoflip/requirements.txt +++ b/pyautoflip/requirements.txt @@ -1,4 +1,1888 @@ -pyautoflip==0.2.1 -fastapi==0.115.12 -uvicorn[standard]==0.34.2 -pydantic==2.11.3 +# This file was autogenerated by uv via the following command: +# uv pip compile pyautoflip/requirements.in --universal --python-version 3.12 --index https://download.pytorch.org/whl/cpu --index-strategy unsafe-best-match --emit-index-url --generate-hashes --output-file pyautoflip/requirements.txt +--index-url https://pypi.org/simple +--extra-index-url https://download.pytorch.org/whl/cpu + +absl-py==2.5.0 \ + --hash=sha256:0c996f25c0490700fadabe6351630f6111534fa0ae252cc6d2014ea3b141135f \ + --hash=sha256:0f17b89f2a4eaaedc4f28c622998aa690564b3012a396a4ffad0821007fe03ba + # via + # mediapipe + # tensorboard +annotated-types==0.8.0 \ + --hash=sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7 \ + --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0 + # via pydantic +anyio==4.14.2 \ + --hash=sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494 \ + --hash=sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f + # via + # starlette + # watchfiles +certifi==2026.7.22 \ + --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \ + --hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55 + # via + # mediapipe + # requests +cffi==2.1.1 \ + --hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \ + --hash=sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66 \ + --hash=sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2 \ + --hash=sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0 \ + --hash=sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6 \ + --hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \ + --hash=sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c \ + --hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \ + --hash=sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9 \ + --hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \ + --hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \ + --hash=sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80 \ + --hash=sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f \ + --hash=sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1 \ + --hash=sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29 \ + --hash=sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8 \ + --hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \ + --hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \ + --hash=sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48 \ + --hash=sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813 \ + --hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \ + --hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \ + --hash=sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6 \ + --hash=sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1 \ + --hash=sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659 \ + --hash=sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688 \ + --hash=sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004 \ + --hash=sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0 \ + --hash=sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062 \ + --hash=sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779 \ + --hash=sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94 \ + --hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \ + --hash=sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab \ + --hash=sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac \ + --hash=sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6 \ + --hash=sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676 \ + --hash=sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1 \ + --hash=sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9 \ + --hash=sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf \ + --hash=sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13 \ + --hash=sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e \ + --hash=sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e \ + --hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \ + --hash=sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527 \ + --hash=sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72 \ + --hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \ + --hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \ + --hash=sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990 \ + --hash=sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd \ + --hash=sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9 \ + --hash=sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94 \ + --hash=sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3 \ + --hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \ + --hash=sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41 \ + --hash=sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5 \ + --hash=sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c \ + --hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \ + --hash=sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4 \ + --hash=sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e \ + --hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \ + --hash=sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98 \ + --hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \ + --hash=sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1 \ + --hash=sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03 \ + --hash=sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af \ + --hash=sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231 \ + --hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \ + --hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \ + --hash=sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836 \ + --hash=sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5 \ + --hash=sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399 \ + --hash=sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96 \ + --hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \ + --hash=sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be \ + --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \ + --hash=sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc \ + --hash=sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455 \ + --hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \ + --hash=sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12 \ + --hash=sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b \ + --hash=sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7 \ + --hash=sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692 \ + --hash=sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54 \ + --hash=sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3 \ + --hash=sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b \ + --hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \ + --hash=sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d \ + --hash=sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358 \ + --hash=sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a \ + --hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \ + --hash=sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc \ + --hash=sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960 \ + --hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \ + --hash=sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb \ + --hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \ + --hash=sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa \ + --hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \ + --hash=sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3 \ + --hash=sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4 \ + --hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264 + # via sounddevice +charset-normalizer==3.4.9 \ + --hash=sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380 \ + --hash=sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62 \ + --hash=sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c \ + --hash=sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226 \ + --hash=sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5 \ + --hash=sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833 \ + --hash=sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b \ + --hash=sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99 \ + --hash=sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501 \ + --hash=sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec \ + --hash=sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698 \ + --hash=sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4 \ + --hash=sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a \ + --hash=sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3 \ + --hash=sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2 \ + --hash=sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a \ + --hash=sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e \ + --hash=sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4 \ + --hash=sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419 \ + --hash=sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84 \ + --hash=sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da \ + --hash=sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519 \ + --hash=sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe \ + --hash=sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381 \ + --hash=sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29 \ + --hash=sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614 \ + --hash=sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0 \ + --hash=sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe \ + --hash=sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29 \ + --hash=sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0 \ + --hash=sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917 \ + --hash=sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9 \ + --hash=sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32 \ + --hash=sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94 \ + --hash=sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63 \ + --hash=sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd \ + --hash=sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198 \ + --hash=sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde \ + --hash=sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012 \ + --hash=sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1 \ + --hash=sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15 \ + --hash=sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b \ + --hash=sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993 \ + --hash=sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4 \ + --hash=sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5 \ + --hash=sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8 \ + --hash=sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35 \ + --hash=sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642 \ + --hash=sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2 \ + --hash=sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d \ + --hash=sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9 \ + --hash=sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c \ + --hash=sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33 \ + --hash=sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db \ + --hash=sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf \ + --hash=sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9 \ + --hash=sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee \ + --hash=sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84 \ + --hash=sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44 \ + --hash=sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f \ + --hash=sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9 \ + --hash=sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9 \ + --hash=sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177 \ + --hash=sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8 \ + --hash=sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b \ + --hash=sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39 \ + --hash=sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41 \ + --hash=sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0 \ + --hash=sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616 \ + --hash=sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d \ + --hash=sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba \ + --hash=sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2 \ + --hash=sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b \ + --hash=sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9 \ + --hash=sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b \ + --hash=sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209 \ + --hash=sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48 \ + --hash=sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046 \ + --hash=sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632 \ + --hash=sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a \ + --hash=sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2 \ + --hash=sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1 \ + --hash=sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b \ + --hash=sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990 \ + --hash=sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5 \ + --hash=sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b \ + --hash=sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9 \ + --hash=sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534 \ + --hash=sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81 \ + --hash=sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a \ + --hash=sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d \ + --hash=sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf \ + --hash=sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115 + # via requests +click==8.4.2 \ + --hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \ + --hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76 + # via + # scenedetect + # uvicorn +colorama==0.4.6 ; sys_platform == 'win32' \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via + # click + # tqdm + # uvicorn +contourpy==1.3.3 \ + --hash=sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69 \ + --hash=sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc \ + --hash=sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880 \ + --hash=sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a \ + --hash=sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8 \ + --hash=sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc \ + --hash=sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470 \ + --hash=sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5 \ + --hash=sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263 \ + --hash=sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b \ + --hash=sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5 \ + --hash=sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381 \ + --hash=sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3 \ + --hash=sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4 \ + --hash=sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e \ + --hash=sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f \ + --hash=sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772 \ + --hash=sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286 \ + --hash=sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42 \ + --hash=sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301 \ + --hash=sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77 \ + --hash=sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7 \ + --hash=sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411 \ + --hash=sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1 \ + --hash=sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9 \ + --hash=sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a \ + --hash=sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b \ + --hash=sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db \ + --hash=sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6 \ + --hash=sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620 \ + --hash=sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989 \ + --hash=sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea \ + --hash=sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67 \ + --hash=sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5 \ + --hash=sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d \ + --hash=sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36 \ + --hash=sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99 \ + --hash=sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1 \ + --hash=sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e \ + --hash=sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b \ + --hash=sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8 \ + --hash=sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d \ + --hash=sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7 \ + --hash=sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7 \ + --hash=sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339 \ + --hash=sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1 \ + --hash=sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659 \ + --hash=sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4 \ + --hash=sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f \ + --hash=sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20 \ + --hash=sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36 \ + --hash=sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb \ + --hash=sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d \ + --hash=sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8 \ + --hash=sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0 \ + --hash=sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b \ + --hash=sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7 \ + --hash=sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe \ + --hash=sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77 \ + --hash=sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497 \ + --hash=sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd \ + --hash=sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1 \ + --hash=sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216 \ + --hash=sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13 \ + --hash=sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae \ + --hash=sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae \ + --hash=sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77 \ + --hash=sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3 \ + --hash=sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f \ + --hash=sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff \ + --hash=sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9 \ + --hash=sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a + # via matplotlib +cycler==0.12.1 \ + --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \ + --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c + # via matplotlib +fastapi==0.115.12 \ + --hash=sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681 \ + --hash=sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d + # via -r requirements.in +filelock==3.32.2 \ + --hash=sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82 \ + --hash=sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8 + # via torch +flatbuffers==25.12.19 \ + --hash=sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4 + # via + # mediapipe + # onnxruntime +fonttools==4.63.0 \ + --hash=sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69 \ + --hash=sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c \ + --hash=sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac \ + --hash=sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096 \ + --hash=sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d \ + --hash=sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68 \ + --hash=sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616 \ + --hash=sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78 \ + --hash=sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f \ + --hash=sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b \ + --hash=sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b \ + --hash=sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02 \ + --hash=sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d \ + --hash=sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f \ + --hash=sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8 \ + --hash=sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272 \ + --hash=sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49 \ + --hash=sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419 \ + --hash=sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001 \ + --hash=sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03 \ + --hash=sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196 \ + --hash=sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9 \ + --hash=sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e \ + --hash=sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5 \ + --hash=sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007 \ + --hash=sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380 \ + --hash=sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8 \ + --hash=sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27 \ + --hash=sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40 \ + --hash=sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e \ + --hash=sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0 \ + --hash=sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263 \ + --hash=sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb \ + --hash=sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94 \ + --hash=sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b \ + --hash=sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6 \ + --hash=sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579 \ + --hash=sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4 \ + --hash=sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59 \ + --hash=sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0 \ + --hash=sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e \ + --hash=sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be \ + --hash=sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd \ + --hash=sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18 \ + --hash=sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22 \ + --hash=sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0 \ + --hash=sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b \ + --hash=sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b \ + --hash=sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af \ + --hash=sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745 + # via matplotlib +fsspec==2026.7.0 \ + --hash=sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279 \ + --hash=sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88 + # via torch +grpcio==1.83.0 \ + --hash=sha256:009667eaf3dcd5224c713589cdc98e7ca4ed0ff0b61132c6b276e930eb83a2df \ + --hash=sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867 \ + --hash=sha256:1aa567f8c3f19850ffd5d2858c9a8ea7c80f0db6c01186b71eb31e923ec984f5 \ + --hash=sha256:1c699bbb20f143c8f2bff219de578aa2dc1f919399d67dc702b038b986ee62df \ + --hash=sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930 \ + --hash=sha256:2b5e75c34842cd9c1b95285ca395c6a569664b81e3ffa6b714125922942abaaf \ + --hash=sha256:2bb48cb5e6dd005ca12b89ce4b6ac0b48ff3112c747542ee7986ef611a8ca6d9 \ + --hash=sha256:32e11c37f5285b0c6fa3042c05fe06903696689749833fc64e67dec71b9bbe33 \ + --hash=sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da \ + --hash=sha256:35a5b1c192496b6c25956eebfa963468935612206fd2543ac3ce981e6a5e0f03 \ + --hash=sha256:3f351629f6ae16ecc0ec3553e586a6763ffd9f6114044286d0cbec3e09241bfa \ + --hash=sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223 \ + --hash=sha256:4e3eedfc92b6b9f2960115e7e620cf0cbf80bb7849a51ce3820dc54dfd88b6b9 \ + --hash=sha256:4fcaa7c45c45b4a89e2867d1f1785d9481a788399d915e341ed2eb49aeef9dd4 \ + --hash=sha256:5882c1a721b50ce0123ee5e839e1ab059ad72a7ade76cdf2d5bd833b56791acf \ + --hash=sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881 \ + --hash=sha256:61007cd08640abc5c54547ee32505474c482cd733a53cb87551ea81faa6350af \ + --hash=sha256:62003babc444a606dcd1f009cd16391ce23669ae4ad6ec267a873da7937a69f5 \ + --hash=sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0 \ + --hash=sha256:6755ed67cc3e454d51ae9f6e1915b80d3942fa4de956ef48dacd45ab7f40b727 \ + --hash=sha256:6b6c666a1d5613ff360c9e90f44665e3a88b25a815209ddbc0917eec281931cb \ + --hash=sha256:6be5c807b717be3dd649446f021301fd7907e376318675d2147823071034112a \ + --hash=sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735 \ + --hash=sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16 \ + --hash=sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49 \ + --hash=sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1 \ + --hash=sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24 \ + --hash=sha256:7936f2a56cf04f6514705c0fedf400971de01b6aa1719327e4718f410a765e2b \ + --hash=sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f \ + --hash=sha256:8f6c395e493d20c39b29392ca200e9aaeb78d0bc2f04db0c0a7da7ddc939aa57 \ + --hash=sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf \ + --hash=sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f \ + --hash=sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c \ + --hash=sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969 \ + --hash=sha256:aeb339838db07600481ef869507279b75326c75eac6d10f7afa62a0da1d2bcdd \ + --hash=sha256:b0a0be840e51b6b7ee9df9269770faf77bdf4b771053c257c21d12bad607714c \ + --hash=sha256:bb669918fd88936b15599caff4160a77ab74bdeb25f2231f6e45b61282d6107b \ + --hash=sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61 \ + --hash=sha256:c19b454d3d3f28db81f2c7c4dbaee96e7f6fd149721733ffe79d6bc530f17404 \ + --hash=sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617 \ + --hash=sha256:c834e86d8fd2f03d7e4db49a027f7c5b89c5b88eed305543a5295bd6fee61e40 \ + --hash=sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45 \ + --hash=sha256:cb2906c61db4f9c64cc360054b5df70eeb81846228e9e56a4944bd415a63dadc \ + --hash=sha256:d05ff664100d429335b93c91b8b34ddf9e94a112205e7fa06dede309e44a4e4c \ + --hash=sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745 \ + --hash=sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45 \ + --hash=sha256:f47d62808b4c0a97b78bff88a6d4ca283a2a492b9a04a87d814af95ca3b9c19c \ + --hash=sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0 \ + --hash=sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d \ + --hash=sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9 \ + --hash=sha256:fba099b716e73512d61b97f71ea3c31a72abb36904036e316bf4dd148ca8dcc8 + # via tensorboard +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via uvicorn +httptools==0.8.0 \ + --hash=sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683 \ + --hash=sha256:0ea897f0c729581ebf72131a438a7932d9b14efef72d75ada966700cac3caaeb \ + --hash=sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b \ + --hash=sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527 \ + --hash=sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124 \ + --hash=sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca \ + --hash=sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081 \ + --hash=sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c \ + --hash=sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77 \ + --hash=sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09 \ + --hash=sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f \ + --hash=sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085 \ + --hash=sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376 \ + --hash=sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5 \ + --hash=sha256:5d7fa4ba7292c1139c0526f0b5aad507c6263c948206ea1b1cbca015c8af1b62 \ + --hash=sha256:5eb911c515b96ee44bbd861e42cbefc488681d450545b1d02127f6136e3a86f5 \ + --hash=sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8 \ + --hash=sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681 \ + --hash=sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999 \ + --hash=sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1 \ + --hash=sha256:7b71e7d7031928c650e1006e6c03e911bf967f7c69c011d37d541c3e7bf55005 \ + --hash=sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d \ + --hash=sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d \ + --hash=sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d \ + --hash=sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d \ + --hash=sha256:9878eb2785ba5eb70631ad269b37976f73d647955e26c91d490eb8a4edfda4ba \ + --hash=sha256:9fc1644f415372cec4f8a5be3a64183737398f10dbb1263602a036427fe75247 \ + --hash=sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745 \ + --hash=sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07 \ + --hash=sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b \ + --hash=sha256:a6f21e2a3b0067bbe7f67e34cfd16276af556e5e52f4c7503be0cb5f90e905e4 \ + --hash=sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2 \ + --hash=sha256:b205e5f5523fa039679da0dfe5a10132b2a4abeae6a86fdd1ddc035f7f836557 \ + --hash=sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d \ + --hash=sha256:bf3b6f807c8541503cecfbb8a8dffb385640d0d96102f3d112aa8740f9b7c826 \ + --hash=sha256:c08ffe3e79756e0963cbc8fe410139f38a5884874b6f2e17761bef6563fdcd9b \ + --hash=sha256:c0d726cc107fceb7d45f978483b4b70dd8caa836f5914d3434bb18628eb73813 \ + --hash=sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0 \ + --hash=sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150 \ + --hash=sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e \ + --hash=sha256:da684f2e1aa2ee9bdcb083f3f3a68c5956750b375bc5df864d3a5f0c42a40b77 \ + --hash=sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568 \ + --hash=sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6 \ + --hash=sha256:df31ef5494f406ab6cf827b7e64a22841c6e2d654100e6a116ea15b46d02d5e8 \ + --hash=sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b \ + --hash=sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7 \ + --hash=sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168 \ + --hash=sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a \ + --hash=sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0 \ + --hash=sha256:fe2a4c95aeba2209434e7b31172da572846cae8ca0bf1e7013e61b99fbbf5e72 + # via uvicorn +idna==3.18 \ + --hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \ + --hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848 + # via + # anyio + # requests +imageio==2.37.4 \ + --hash=sha256:1ab2e22c8debf700f24c3ac43e8f95f3b3a8110c83b93411e97b4b0b2cd1c7e6 \ + --hash=sha256:e45cbc5e83502047fb138f7f585f7f105a136a57eea5f4b3cfc6ce1b52720bd3 + # via scikit-image +insightface==1.0.1 \ + --hash=sha256:27af24891bbba470cb3573b366a0fcca8989fc8503c9f8f281e8cba6fd716075 \ + --hash=sha256:5f373f6fedbdda5cbc59a34ca386a75a2995cdaf6899402590ae9eb4308fc2e8 + # via pyautoflip +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via torch +joblib==1.5.3 \ + --hash=sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713 \ + --hash=sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3 + # via scikit-learn +kiwisolver==1.5.0 \ + --hash=sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9 \ + --hash=sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679 \ + --hash=sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0 \ + --hash=sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8 \ + --hash=sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276 \ + --hash=sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96 \ + --hash=sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e \ + --hash=sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac \ + --hash=sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f \ + --hash=sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a \ + --hash=sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15 \ + --hash=sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7 \ + --hash=sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368 \ + --hash=sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02 \ + --hash=sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9 \ + --hash=sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681 \ + --hash=sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57 \ + --hash=sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27 \ + --hash=sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4 \ + --hash=sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920 \ + --hash=sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374 \ + --hash=sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3 \ + --hash=sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa \ + --hash=sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23 \ + --hash=sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859 \ + --hash=sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb \ + --hash=sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d \ + --hash=sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc \ + --hash=sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581 \ + --hash=sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c \ + --hash=sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099 \ + --hash=sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05 \ + --hash=sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9 \ + --hash=sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd \ + --hash=sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc \ + --hash=sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796 \ + --hash=sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303 \ + --hash=sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca \ + --hash=sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314 \ + --hash=sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489 \ + --hash=sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57 \ + --hash=sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1 \ + --hash=sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797 \ + --hash=sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021 \ + --hash=sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db \ + --hash=sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22 \ + --hash=sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028 \ + --hash=sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083 \ + --hash=sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65 \ + --hash=sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588 \ + --hash=sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0 \ + --hash=sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a \ + --hash=sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1 \ + --hash=sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c \ + --hash=sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac \ + --hash=sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476 \ + --hash=sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53 \ + --hash=sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3 \ + --hash=sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4 \ + --hash=sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615 \ + --hash=sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb \ + --hash=sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18 \ + --hash=sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b \ + --hash=sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1 \ + --hash=sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2 \ + --hash=sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c \ + --hash=sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac \ + --hash=sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d \ + --hash=sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf \ + --hash=sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2 \ + --hash=sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f \ + --hash=sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f \ + --hash=sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4 \ + --hash=sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9 \ + --hash=sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e \ + --hash=sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737 \ + --hash=sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b \ + --hash=sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed \ + --hash=sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3 \ + --hash=sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7 \ + --hash=sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08 \ + --hash=sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e \ + --hash=sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902 \ + --hash=sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd \ + --hash=sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6 \ + --hash=sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310 \ + --hash=sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537 \ + --hash=sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554 \ + --hash=sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e \ + --hash=sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87 \ + --hash=sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a \ + --hash=sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c \ + --hash=sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79 \ + --hash=sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e \ + --hash=sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16 \ + --hash=sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1 \ + --hash=sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875 \ + --hash=sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd \ + --hash=sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0 \ + --hash=sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9 \ + --hash=sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646 \ + --hash=sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657 \ + --hash=sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4 \ + --hash=sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232 \ + --hash=sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819 \ + --hash=sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384 \ + --hash=sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309 \ + --hash=sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede \ + --hash=sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2 \ + --hash=sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203 \ + --hash=sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7 \ + --hash=sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df \ + --hash=sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c \ + --hash=sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167 \ + --hash=sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3 \ + --hash=sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09 \ + --hash=sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398 + # via matplotlib +lazy-loader==0.5 \ + --hash=sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3 \ + --hash=sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005 + # via scikit-image +markdown==3.10.3 \ + --hash=sha256:3589362618f743188b4d955b874402bc814f4f83f544dc207719f4baa7d9c45f \ + --hash=sha256:fa6c92a00a4a3c98b22728c64a935ae1928250ae65058a6ded814d2cc29a4cea + # via tensorboard +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via + # jinja2 + # werkzeug +matplotlib==3.11.1 \ + --hash=sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2 \ + --hash=sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464 \ + --hash=sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1 \ + --hash=sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb \ + --hash=sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb \ + --hash=sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a \ + --hash=sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b \ + --hash=sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1 \ + --hash=sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3 \ + --hash=sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2 \ + --hash=sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741 \ + --hash=sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a \ + --hash=sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987 \ + --hash=sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be \ + --hash=sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf \ + --hash=sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f \ + --hash=sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191 \ + --hash=sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18 \ + --hash=sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b \ + --hash=sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30 \ + --hash=sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f \ + --hash=sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e \ + --hash=sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f \ + --hash=sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f \ + --hash=sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f \ + --hash=sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda \ + --hash=sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481 \ + --hash=sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6 \ + --hash=sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099 \ + --hash=sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78 \ + --hash=sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2 \ + --hash=sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319 \ + --hash=sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685 \ + --hash=sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83 \ + --hash=sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3 \ + --hash=sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407 \ + --hash=sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2 \ + --hash=sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d \ + --hash=sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea \ + --hash=sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472 \ + --hash=sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f \ + --hash=sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c \ + --hash=sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae \ + --hash=sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74 \ + --hash=sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99 \ + --hash=sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb + # via + # mediapipe + # pyautoflip +mediapipe==1.0.0 \ + --hash=sha256:01260f58fedd2cbebe69f4d4fcbc6327aba17090e19ef6a03c7afda084cd64e1 \ + --hash=sha256:07a449446bf888a8a2787dbf6fc1a33da4c47977313deec64d13c35bff41f6d2 \ + --hash=sha256:7ee4783be41b2de345e1eb71e2f7e7c159a50ed5c283e60ccb8f5a6027c70a82 \ + --hash=sha256:da57e6719bbab05007272c91d6ca2e0e2e370709491cbe344a372f87e25cf604 \ + --hash=sha256:e57d9a606723b2c77a51bb1d194c8eb736a84c552d24578a8536f47f656bb241 + # via pyautoflip +ml-dtypes==0.5.4 \ + --hash=sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf \ + --hash=sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d \ + --hash=sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f \ + --hash=sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483 \ + --hash=sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7 \ + --hash=sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22 \ + --hash=sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6 \ + --hash=sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175 \ + --hash=sha256:388d399a2152dd79a3f0456a952284a99ee5c93d3e2f8dfe25977511e0515270 \ + --hash=sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1 \ + --hash=sha256:3d277bf3637f2a62176f4575512e9ff9ef51d00e39626d9fe4a161992f355af2 \ + --hash=sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1 \ + --hash=sha256:4ff7f3e7ca2972e7de850e7b8fcbb355304271e2933dd90814c1cb847414d6e2 \ + --hash=sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298 \ + --hash=sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d \ + --hash=sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de \ + --hash=sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049 \ + --hash=sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d \ + --hash=sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90 \ + --hash=sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb \ + --hash=sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465 \ + --hash=sha256:88c982aac7cb1cbe8cbb4e7f253072b1df872701fcaf48d84ffbb433b6568f24 \ + --hash=sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453 \ + --hash=sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56 \ + --hash=sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48 \ + --hash=sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff \ + --hash=sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460 \ + --hash=sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac \ + --hash=sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900 \ + --hash=sha256:a9b61c19040397970d18d7737375cffd83b1f36a11dd4ad19f83a016f736c3ef \ + --hash=sha256:b4b801ebe0b477be666696bda493a9be8356f1f0057a57f1e35cd26928823e5a \ + --hash=sha256:b95e97e470fe60ed493fd9ae3911d8da4ebac16bd21f87ffa2b7c588bf22ea2c \ + --hash=sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040 \ + --hash=sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9 \ + --hash=sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7 \ + --hash=sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6 \ + --hash=sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b \ + --hash=sha256:d81fdb088defa30eb37bf390bb7dde35d3a83ec112ac8e33d75ab28cc29dd8b0 \ + --hash=sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328 + # via onnx +mpmath==1.3.0 \ + --hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \ + --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c + # via sympy +narwhals==2.24.0 \ + --hash=sha256:42fdedf44e5b2ca7505630d45b4ac3058f38d8485cba9fe1652ca23152df7489 \ + --hash=sha256:b5c0f684ccd9d7475b564111e319a4964abcf2baf79d3cf6b1003d06ac9b828d + # via scikit-learn +networkx==3.6.1 \ + --hash=sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509 \ + --hash=sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 + # via + # scikit-image + # torch +numpy==2.5.2 \ + --hash=sha256:0090ccdd57ec2703e9b49d0bf554767370581c1dd0a6b2bb2b2d9def317d042a \ + --hash=sha256:078f9b027b478c9379b9677babbf0f8b8f1ecfada27636d7b9a93990c638739f \ + --hash=sha256:07d4e89f3a9ab0a9ba24264ccdb642b3dd951b2281e8883a5481a4aa79cc31a7 \ + --hash=sha256:0a4035ae1129ff8777f08bfbd44f1e5d8e9c049ce0c2dd78fc0d92c13e7251c0 \ + --hash=sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3 \ + --hash=sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c \ + --hash=sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce \ + --hash=sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8 \ + --hash=sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1 \ + --hash=sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4 \ + --hash=sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee \ + --hash=sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740 \ + --hash=sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98 \ + --hash=sha256:2ffa7bacab3e2ee1b19ed31766bb60bb380b68c23f051e199c5cc598afd68710 \ + --hash=sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee \ + --hash=sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68 \ + --hash=sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf \ + --hash=sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8 \ + --hash=sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf \ + --hash=sha256:40f4d451aed46a8046a1aae41c4e55fb3612273df9c502480135e1501576a34b \ + --hash=sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884 \ + --hash=sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03 \ + --hash=sha256:4ec954036759bcee3aa484f8603bd9c14f3e776293b85578b8734c2d72777c69 \ + --hash=sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4 \ + --hash=sha256:50a68f4bacd8a2b33d8da3d2269d0d78500f86ea582e4786dc10f5ef2c2c6842 \ + --hash=sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65 \ + --hash=sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080 \ + --hash=sha256:5f8e00be2ec6f45f4e8a41a527f68d44a7d96fee92a650e4d8b1326f77f61e6e \ + --hash=sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e \ + --hash=sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414 \ + --hash=sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59 \ + --hash=sha256:6a9bb119fb8dd21ba30b3f0e555b7e2b081bd9883af21ec9c1c633d161cda3a8 \ + --hash=sha256:6b588cc8f902d6bff201c19fd00c43ab8545671e3554d014e12e14139e5e8617 \ + --hash=sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4 \ + --hash=sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb \ + --hash=sha256:7354826bc6f8f69402e9b7fe28d15fcd34feebd74f856f111585c5b0c9fb0251 \ + --hash=sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d \ + --hash=sha256:77843ca236b777e67f8d6b3660ea116e499612703a0ecd7093f316201eb9d8e2 \ + --hash=sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab \ + --hash=sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657 \ + --hash=sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15 \ + --hash=sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9 \ + --hash=sha256:9a0731745a72a184490a582fb4af2533512bd071ace67785b5fdffc0ae58dce8 \ + --hash=sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323 \ + --hash=sha256:a610dc7e3c52edd39c2bc2375ff9c3fd59cb3ad00e4472d36f83bc1457145788 \ + --hash=sha256:a839318485284a6fb31be4f8f2c91c8f2cb22f4543c4a8903f12b0671ffe07cc \ + --hash=sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56 \ + --hash=sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1 \ + --hash=sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d \ + --hash=sha256:ba0a474801b8dc67b66bf465548abc90e82b44d2611b5770f33008dcabffe8ec \ + --hash=sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2 \ + --hash=sha256:c081cbe16ba1ab53078e5ff29013621e33c509eedab055775d956427712c236e \ + --hash=sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7 \ + --hash=sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26 \ + --hash=sha256:cf7de32f486e4ac9e2d93b810f9e9ac72a728dd46a32a0bb403222f27f653514 \ + --hash=sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860 \ + --hash=sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a \ + --hash=sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1 \ + --hash=sha256:dc649493697006bc90614a5f0bbc8cb3cb1866715c474e473694968d7e6b99ab \ + --hash=sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba \ + --hash=sha256:e5651f3f87add730ee6608d915009e19c911fba0cb000c7e3ea994b7d768eb12 \ + --hash=sha256:e79aba74ffaf5f78a050d777c184cddf8fdffabab38acf5f3ef1fecbc17895d6 \ + --hash=sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e \ + --hash=sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac \ + --hash=sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb \ + --hash=sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f + # via + # contourpy + # imageio + # insightface + # matplotlib + # mediapipe + # ml-dtypes + # onnx + # onnxruntime + # opencv-contrib-python + # opencv-python + # pyautoflip + # scenedetect + # scikit-image + # scikit-learn + # scipy + # tensorboard + # tensorboardx + # tifffile + # torchvision +onnx==1.22.0 \ + --hash=sha256:19e45e4af88e3fe3261458d4b8cc461957ae2782a358a3560503569bf3b23b72 \ + --hash=sha256:1d0a2bdb15eb2b3cb65c438f3423d9620d14fdce32f92380e6bb1b2e09568ef5 \ + --hash=sha256:239958534464612fbcb6ed23d5228aaa925b39b8773f58726809ffdccb4edd1c \ + --hash=sha256:2632406b8f523ef2e2873c363f90b20a3d88c0fbcfac757d3addffccf8f452c2 \ + --hash=sha256:2d8f229a553fa440fe623ed7b36fca5e7762da3af871c3f8f8ce451df73e2914 \ + --hash=sha256:33ce94119bbb7f05d9caea4ea7549f5185a54369f6bbc9f70171bd5ee6935bbc \ + --hash=sha256:596fbf0490947533c1c1045ba860851dc9fb77471023dac9a71ba5b42ceab103 \ + --hash=sha256:5c1c0408a9d4b4df33851672e5fc7590b96301ee123396d608f9ab6f045ab06b \ + --hash=sha256:6d0ffffd63a4ecc21ddaeddd5bf02099cb701aa4243f2de00122726869065ca4 \ + --hash=sha256:72ccebab3bac07215c204ce8848d42e78eaaa666badbf72d25cd359b9f269e3a \ + --hash=sha256:82e9f27fc1223cb06d68a56bed6f9d3caf3d0dad1b61bce45006d529b15bd94c \ + --hash=sha256:8561a2c00041c07e08db0c228593b5b4694100398685f348532af7dbb84189da \ + --hash=sha256:87a3077958f66f9a26dec10077ac28326d9cec2cbe1f0b040947243449754573 \ + --hash=sha256:8907b9b9389893bc0dc6314cc00ee1e3a69844e48d689eacc6a0340411a7da58 \ + --hash=sha256:8a5eccce2d5fc6c5046928a9aa7cdd9750ea4a586f8de341d3d40d820c35fdec \ + --hash=sha256:8e268cdc0547e3949799ffd4a44451dc2b9080b57d0824a2db680b6ec65506f0 \ + --hash=sha256:955e02e1f6d385b53d52f9cd7b9cdf5caf417c300bcfe3c64c6d542be763845b \ + --hash=sha256:a1a89a7cb9ba13d78f009bdec448ec82a98972589734f157022a2bff7a5973a6 \ + --hash=sha256:a3a39fc4643867aecb33417fdddb11e308ee79d2d4a584b9d50cc7aec2091b13 \ + --hash=sha256:ae5a563f281cd9d2845622cecf6c092a57e4ee1b138f66fdbbdd4200567a5e16 \ + --hash=sha256:c21a0e59fd967a95b358e4a6e756d1f1eec2d304a83480f329f66e30d2bf0223 \ + --hash=sha256:cc8b66b312f8f03a53e268afb67180a2d97dd12cc79e2b61361c6c0073448016 \ + --hash=sha256:ef40c0aaf0b643857ea9306fc7eddce17eaf9fb0407e4801f1fc5758443a38e0 \ + --hash=sha256:f3c120dcdb70ad738f3c061b32798f408ea299eb69f84dd69ab4a6bf3c2ec01f + # via insightface +onnxruntime==1.28.0 \ + --hash=sha256:07fb3cbe990d6bf0ab3c22bfbbfb0e314151266046ea6edb4a07f556b4258c5f \ + --hash=sha256:0a83bdb70d143cede762b677789bf2a7acca54b3fb82565601d5c30695aa933c \ + --hash=sha256:0d650aeee29368414367b65529e90afe4bf1bab76254789063b8b2f7ea3013c8 \ + --hash=sha256:0faf85fb447a663c9cdadc39bd6b19bdf7bedded6699e45731b9b36c46fd993d \ + --hash=sha256:1a1a19175464665c9b8d50bc916f216cc0b569110045b7bbca8f9f290b186f58 \ + --hash=sha256:26ff0fdd06efb6c155bae95387a09db1a2be89c7a03e4d0bffd5a171cc2826da \ + --hash=sha256:31410f544674f534c2f27348af52ef81682ca9c8719154bf4d48f0ef23823b1e \ + --hash=sha256:4e81a23df16e7acb9d51b06d30cc098e49315ef9180f97bc2221d167b4b04d9c \ + --hash=sha256:4f6e92367ddce1e4d33cf295024f40192be6c6171a09208f515ba169ced06c8e \ + --hash=sha256:54fa221d669282bd8f582708ce4c96010a7e9fb0661f9006b37fe2fedafb73fe \ + --hash=sha256:6afdc83f1317c136e92fc29f5ee9f058de59d87c0b22cee3fdbfbaa0ccc2098a \ + --hash=sha256:8adff67a3f28257b37cfe945a7e952e4122666aa8c91a0380862e9fd4c2ed19f \ + --hash=sha256:8d66f9ceb29909c70839e4e4fb3435c7b490050d8f162bd5f3aba4ca01ee517f \ + --hash=sha256:a166b78ee04f3a37fa1ef82034b6a3ce96d9684e582d4d30b296de83e9998bb5 \ + --hash=sha256:ac301f53b1930402fc46c368e268acfed02f3207272aaff05070d7e09f96f031 \ + --hash=sha256:bc2565e487b4896fb988d6383577d875d958e071fc5f6c3550bd5d02ae98264b \ + --hash=sha256:c35064f9b3c43c81c5d5d282091401d0f1ff22796d93ccade4ea2ece5e137ab8 \ + --hash=sha256:cfab507abe09d6ffeb817eee07944d452fdc0b00fdcef34cab4db10a45e378c7 \ + --hash=sha256:e02feeb0165c5f13b4cc954738078d59b90128516ac12b671ee24a530242bf02 \ + --hash=sha256:e562d6e36a749f6764481c0ddb0f2af3d0b5a3c164291361d08803c557f369af \ + --hash=sha256:f2a3b9e30ce880d4ca54999cb313569e36da4f62eefe25f87be18f43e9a3a4d5 \ + --hash=sha256:f5c5daabd28aad610f83fdcf32acec8fb57e6adc6c6a39fe2a3c755db957b410 \ + --hash=sha256:f649dd6f6452d12a8059888aa489fe519e062e18793dac72b9efa0f9fdb64135 \ + --hash=sha256:f7f022a1103cae591c75fc4565589a515f2ddd14a6ac8e8a05812dfeda142e28 + # via + # insightface + # pyautoflip +opencv-contrib-python==5.0.0.93 \ + --hash=sha256:29b916da864002a921c79b6df4cfb2dcf79870f166ddf3ce1179b558bc7313d7 \ + --hash=sha256:3ebb8e0506573f36f54038116321a086c97aeda8ef154198931f3ea18b435cc7 \ + --hash=sha256:45a1ce7c68828907348e649edd471a7dc5244d3d0ac21989e67c63d2f4fa629a \ + --hash=sha256:461622db95c964652d4d8fda171034961c3de270f78a6095aaad31050771774a \ + --hash=sha256:8427dcb0561dc3ba32f3771c627f52b87c29932c265bea28ffcb54804c3c3fec \ + --hash=sha256:b84f0b0fcdbd2421b5819e517542463e71eed7f41e0a0c4ec280ed88b7269a66 \ + --hash=sha256:cac609c9a4fce67feb287837c671a2c4da9467df8e47fa0cce7bdc453d12a529 \ + --hash=sha256:da0ba61096b08c63cb4440d8fd6f323835cbb78b714c5ba22a39e1db68c83166 \ + --hash=sha256:dd8a80a04a8610c033757135b7846ad2027ba2d3bf9faf2f83f3dc6ed9e3814a + # via mediapipe +opencv-python==5.0.0.93 \ + --hash=sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2 \ + --hash=sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898 \ + --hash=sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157 \ + --hash=sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2 \ + --hash=sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b \ + --hash=sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039 \ + --hash=sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac \ + --hash=sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881 \ + --hash=sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2 + # via + # insightface + # pyautoflip + # scenedetect +packaging==26.3 \ + --hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \ + --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c + # via + # lazy-loader + # matplotlib + # onnxruntime + # scikit-image + # tensorboard + # tensorboardx +pillow==12.3.0 \ + --hash=sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756 \ + --hash=sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a \ + --hash=sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59 \ + --hash=sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45 \ + --hash=sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3 \ + --hash=sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df \ + --hash=sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139 \ + --hash=sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b \ + --hash=sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39 \ + --hash=sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e \ + --hash=sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8 \ + --hash=sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1 \ + --hash=sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8 \ + --hash=sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89 \ + --hash=sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5 \ + --hash=sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130 \ + --hash=sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd \ + --hash=sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d \ + --hash=sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b \ + --hash=sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed \ + --hash=sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace \ + --hash=sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb \ + --hash=sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931 \ + --hash=sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510 \ + --hash=sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6 \ + --hash=sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1 \ + --hash=sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce \ + --hash=sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385 \ + --hash=sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e \ + --hash=sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c \ + --hash=sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7 \ + --hash=sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace \ + --hash=sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c \ + --hash=sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f \ + --hash=sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64 \ + --hash=sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f \ + --hash=sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a \ + --hash=sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827 \ + --hash=sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17 \ + --hash=sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4 \ + --hash=sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a \ + --hash=sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701 \ + --hash=sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e \ + --hash=sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91 \ + --hash=sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66 \ + --hash=sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468 \ + --hash=sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217 \ + --hash=sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658 \ + --hash=sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418 \ + --hash=sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a \ + --hash=sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c \ + --hash=sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330 \ + --hash=sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402 \ + --hash=sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09 \ + --hash=sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930 \ + --hash=sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f \ + --hash=sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec \ + --hash=sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a \ + --hash=sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94 \ + --hash=sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468 \ + --hash=sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b \ + --hash=sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965 \ + --hash=sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8 \ + --hash=sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd \ + --hash=sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7 \ + --hash=sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c \ + --hash=sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777 \ + --hash=sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35 \ + --hash=sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9 \ + --hash=sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f \ + --hash=sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f \ + --hash=sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0 \ + --hash=sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c \ + --hash=sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71 \ + --hash=sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3 \ + --hash=sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838 \ + --hash=sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf \ + --hash=sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321 \ + --hash=sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26 \ + --hash=sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec \ + --hash=sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9 \ + --hash=sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65 \ + --hash=sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5 \ + --hash=sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e \ + --hash=sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d \ + --hash=sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198 \ + --hash=sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7 + # via + # imageio + # matplotlib + # pyautoflip + # scikit-image + # tensorboard + # torchvision +platformdirs==4.11.2 \ + --hash=sha256:3a2ae5fca3520a01ab1be8b45613537f52ddf5b5f6f53d88233892dfbf0cd82d \ + --hash=sha256:7f89089b6ea71bda7962953edcf784b2e2d9d285b40ad88be2bb75c6e9d82ab4 + # via scenedetect +protobuf==7.35.1 \ + --hash=sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799 \ + --hash=sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87 \ + --hash=sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6 \ + --hash=sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30 \ + --hash=sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9 \ + --hash=sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4 \ + --hash=sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4 \ + --hash=sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a + # via + # onnx + # onnxruntime + # tensorboard + # tensorboardx +pyautoflip==0.2.1 \ + --hash=sha256:34dfcc6cfbf3c142c927345f18baf5f16219b017997c247ed69bb5205130b629 \ + --hash=sha256:da2cfea93e62b9cf0265c596faa174d7aad65a9800851a8dac0732fd631ee96e + # via -r requirements.in +pycparser==3.0 ; implementation_name != 'PyPy' \ + --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ + --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 + # via cffi +pydantic==2.11.3 \ + --hash=sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3 \ + --hash=sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f + # via + # -r requirements.in + # fastapi +pydantic-core==2.33.1 \ + --hash=sha256:0483847fa9ad5e3412265c1bd72aad35235512d9ce9d27d81a56d935ef489672 \ + --hash=sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1 \ + --hash=sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add \ + --hash=sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068 \ + --hash=sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b \ + --hash=sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505 \ + --hash=sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8 \ + --hash=sha256:177d50460bc976a0369920b6c744d927b0ecb8606fb56858ff542560251b19e5 \ + --hash=sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e \ + --hash=sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544 \ + --hash=sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4 \ + --hash=sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a \ + --hash=sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a \ + --hash=sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1 \ + --hash=sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266 \ + --hash=sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83 \ + --hash=sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764 \ + --hash=sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde \ + --hash=sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26 \ + --hash=sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896 \ + --hash=sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18 \ + --hash=sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939 \ + --hash=sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48 \ + --hash=sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a \ + --hash=sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761 \ + --hash=sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7 \ + --hash=sha256:3f1fdb790440a34f6ecf7679e1863b825cb5ffde858a9197f851168ed08371e5 \ + --hash=sha256:3f2648b9262607a7fb41d782cc263b48032ff7a03a835581abbf7a3bec62bcf5 \ + --hash=sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d \ + --hash=sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e \ + --hash=sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3 \ + --hash=sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db \ + --hash=sha256:5277aec8d879f8d05168fdd17ae811dd313b8ff894aeeaf7cd34ad28b4d77e33 \ + --hash=sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850 \ + --hash=sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde \ + --hash=sha256:5773da0ee2d17136b1f1c6fbde543398d452a6ad2a7b54ea1033e2daa739b8d2 \ + --hash=sha256:5ab77f45d33d264de66e1884fca158bc920cb5e27fd0764a72f72f5756ae8bdb \ + --hash=sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02 \ + --hash=sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c \ + --hash=sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77 \ + --hash=sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504 \ + --hash=sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516 \ + --hash=sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24 \ + --hash=sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a \ + --hash=sha256:723c5630c4259400818b4ad096735a829074601805d07f8cafc366d95786d331 \ + --hash=sha256:7965c13b3967909a09ecc91f21d09cfc4576bf78140b988904e94f130f188396 \ + --hash=sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c \ + --hash=sha256:7edbc454a29fc6aeae1e1eecba4f07b63b8d76e76a748532233c4c167b4cb9ea \ + --hash=sha256:7fb66263e9ba8fea2aa85e1e5578980d127fb37d7f2e292773e7bc3a38fb0c7b \ + --hash=sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969 \ + --hash=sha256:8ab581d3530611897d863d1a649fb0644b860286b4718db919bfd51ece41f10b \ + --hash=sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea \ + --hash=sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927 \ + --hash=sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc \ + --hash=sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e \ + --hash=sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595 \ + --hash=sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d \ + --hash=sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498 \ + --hash=sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe \ + --hash=sha256:9f466e8bf0a62dc43e068c12166281c2eca72121dd2adc1040f3aa1e21ef8599 \ + --hash=sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e \ + --hash=sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89 \ + --hash=sha256:a3edde68d1a1f9af1273b2fe798997b33f90308fb6d44d8550c89fc6a3647cf6 \ + --hash=sha256:a62c3c3ef6a7e2c45f7853b10b5bc4ddefd6ee3cd31024754a1a5842da7d598d \ + --hash=sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523 \ + --hash=sha256:ab0277cedb698749caada82e5d099dc9fed3f906a30d4c382d1a21725777a1e5 \ + --hash=sha256:ad05b683963f69a1d5d2c2bdab1274a31221ca737dbbceaa32bcb67359453cdd \ + --hash=sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d \ + --hash=sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a \ + --hash=sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe \ + --hash=sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df \ + --hash=sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c \ + --hash=sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30 \ + --hash=sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e \ + --hash=sha256:c91dbb0ab683fa0cd64a6e81907c8ff41d6497c346890e26b23de7ee55353f96 \ + --hash=sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f \ + --hash=sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3 \ + --hash=sha256:d100e3ae783d2167782391e0c1c7a20a31f55f8015f3293647544df3f9c67824 \ + --hash=sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde \ + --hash=sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d \ + --hash=sha256:de9e06abe3cc5ec6a2d5f75bc99b0bdca4f5c719a5b34026f8c57efbdecd2ee3 \ + --hash=sha256:df6a94bf9452c6da9b5d76ed229a5683d0306ccb91cca8e1eea883189780d568 \ + --hash=sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961 \ + --hash=sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4 \ + --hash=sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda \ + --hash=sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5 \ + --hash=sha256:e7aaba1b4b03aaea7bb59e1b5856d734be011d3e6d98f5bcaa98cb30f375f2ad \ + --hash=sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db \ + --hash=sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd \ + --hash=sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383 \ + --hash=sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40 \ + --hash=sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f \ + --hash=sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b \ + --hash=sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc \ + --hash=sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5 \ + --hash=sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65 \ + --hash=sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39 \ + --hash=sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89 \ + --hash=sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091 + # via pydantic +pyparsing==3.3.2 \ + --hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \ + --hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc + # via matplotlib +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via matplotlib +python-dotenv==1.2.2 \ + --hash=sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a \ + --hash=sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3 + # via uvicorn +pyyaml==6.0.3 \ + --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ + --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ + --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ + --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ + --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ + --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ + --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ + --hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ + --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ + --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ + --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ + --hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ + --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ + --hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ + --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ + --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ + --hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ + --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ + --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ + --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ + --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ + --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ + --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ + --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ + --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ + --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ + --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ + --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ + --hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ + --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ + --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ + --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ + --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ + --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ + --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ + --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ + --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ + --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ + --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ + --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ + --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ + --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ + --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ + --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ + --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ + --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ + --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ + --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ + --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ + --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ + --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ + --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ + --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ + --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ + --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \ + --hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \ + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ + --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \ + --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \ + --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \ + --hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \ + --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \ + --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ + --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \ + --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \ + --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ + --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ + --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \ + --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ + --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ + --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 + # via uvicorn +requests==2.34.2 \ + --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ + --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed + # via insightface +scenedetect==0.7.1 \ + --hash=sha256:2bc082056f231d35f9d5eed6d8077a5d417c77c2787d965d7f03be3802cb6f7f \ + --hash=sha256:91b67902275b2e0d29a12f6d70435f04e0bd7852a6dc8a69c901c6069328dffa + # via pyautoflip +scikit-image==0.26.0 \ + --hash=sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf \ + --hash=sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650 \ + --hash=sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb \ + --hash=sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581 \ + --hash=sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f \ + --hash=sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9 \ + --hash=sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d \ + --hash=sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e \ + --hash=sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2 \ + --hash=sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394 \ + --hash=sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37 \ + --hash=sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70 \ + --hash=sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44 \ + --hash=sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8 \ + --hash=sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc \ + --hash=sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab \ + --hash=sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c \ + --hash=sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3 \ + --hash=sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59 \ + --hash=sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af \ + --hash=sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd \ + --hash=sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466 \ + --hash=sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21 \ + --hash=sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37 \ + --hash=sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7 \ + --hash=sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5 \ + --hash=sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953 \ + --hash=sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49 \ + --hash=sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604 \ + --hash=sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219 \ + --hash=sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7 \ + --hash=sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0 \ + --hash=sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1 \ + --hash=sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd \ + --hash=sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0 \ + --hash=sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b \ + --hash=sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4 \ + --hash=sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f \ + --hash=sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505 \ + --hash=sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a \ + --hash=sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1 \ + --hash=sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd \ + --hash=sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932 \ + --hash=sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c \ + --hash=sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d \ + --hash=sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578 \ + --hash=sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa \ + --hash=sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331 \ + --hash=sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62 + # via + # insightface + # pyautoflip +scikit-learn==1.9.0 \ + --hash=sha256:051075bda8b7aab87b1906ab3d4740a1e1224a19d7b3781a576736edc94e76aa \ + --hash=sha256:056c92bb67ad4c28463c2f2653d9701449201e7e7a9e94e321be0f71c4fef2b8 \ + --hash=sha256:147e9329ef0e39f75d4cffa02b2aa48d827832684926cd5210d9a2cb5c57246b \ + --hash=sha256:1b944b6db288f6b926e3650026ddafb988929de95d11fc2cc5fa117773c9ba42 \ + --hash=sha256:1fea2cc5677ab49d6f5bade978c866da44957b712d92e9635e8b4f723013c3cb \ + --hash=sha256:24360002ae845e7866522b0a5bbf690802e7bc388cac8663502e78aa98598aa2 \ + --hash=sha256:26e22435f63bcdcf396b574273f29f13dd531f5ea035801f5be10ba1540a4e60 \ + --hash=sha256:2bd41b0d201bc81575531b96b713d3eb5e5f50fb0b82101ff0f92294fdc236ac \ + --hash=sha256:366652351f092b219c248f1e72821e841960a63d8f358f1dcfd54dc1cbdbbc28 \ + --hash=sha256:38c3dcb9a1ffb85505ec53d54c7b4aea0cff70050425a7760c2af661ac85df05 \ + --hash=sha256:4306775fad04cc4b472a1b15af1ae9cede1540fbfcc17fbce3767cd8dc7ae283 \ + --hash=sha256:4ccacf04ca5f4b492158a5f28afe0ace43f81b2571e4b9a66d34848b46128949 \ + --hash=sha256:5162ad10a418c8a282dde04c9aa06965de3e9a65f33c1440c0ae69bb1a09d913 \ + --hash=sha256:5808d98f15c6bf6d9d96d2348c1997392a5888ce7097e664105f930c4bca1277 \ + --hash=sha256:5b934c45c252844a91d69fda3a34cff5e7307e1db10d77cb10a3980312c74713 \ + --hash=sha256:5bad8f8b9950321b54c965fdcbac6c6c55e79e16646b49977bcf3668d3870a1a \ + --hash=sha256:5be45aa4a42a68a533913a6ed736cf309de2226411c79ef8d609a5456f1939b1 \ + --hash=sha256:5dc1818c77575d149e25fce9ef82dd7b7263ae372f03494158668ad632a69759 \ + --hash=sha256:5e50ed4da51974e86e940690e9a3d82e729b62b5a49f7c9bac534d515d39d86f \ + --hash=sha256:64fa347efc1c839c487433e40c5144d38c336e8a2b59c81aa8660373945c2673 \ + --hash=sha256:78fc56eafd4edb9575d2d8950d1dd152061abb573341a1cb7e099fc40f6c6666 \ + --hash=sha256:80746d63bd4b6eaca54d36fe5feaf4d28bb38dc6f9470f81c7cad7c40155f119 \ + --hash=sha256:8833266989d3a5110178a9fae30783675460724d0e1efb13b14901d2c660c557 \ + --hash=sha256:9656acd4e93f74e0b66c8a36c88830a99252dfa900044d36bc2212ae89a47162 \ + --hash=sha256:9db6f4d34e68c8899e4cab27fdf8eafe6ed21f2ba52ceb25ea250cd237f8e47b \ + --hash=sha256:d77f54c017633791bc0225a43e2f8d03745fdcfe4880268fcc4df15f505dec2e \ + --hash=sha256:da76d09304a4706db7cc1e3ebaa3b6b98a67365cc11d2996c4f1e58ba47df714 \ + --hash=sha256:ee1a8db2c18c08e34c7412d4b10be1cac214cd4ea7dc9715a6a327eb49a37c96 \ + --hash=sha256:f401448645a3e7bc115aa3c094097865155b34bff1cba8101857d9104e99074c \ + --hash=sha256:f7e254636164090da847715a27f8e5478feb98c40a9e0ee90cbd277de9e5ceb8 \ + --hash=sha256:fd3a8ef0c758555a3b23c03adaa858af32f7736785ded50ad5991f59c4ed03fa + # via pyautoflip +scipy==1.18.0 \ + --hash=sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446 \ + --hash=sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468 \ + --hash=sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b \ + --hash=sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553 \ + --hash=sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0 \ + --hash=sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7 \ + --hash=sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2 \ + --hash=sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b \ + --hash=sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61 \ + --hash=sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8 \ + --hash=sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8 \ + --hash=sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6 \ + --hash=sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690 \ + --hash=sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de \ + --hash=sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578 \ + --hash=sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab \ + --hash=sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce \ + --hash=sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f \ + --hash=sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520 \ + --hash=sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378 \ + --hash=sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197 \ + --hash=sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709 \ + --hash=sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132 \ + --hash=sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867 \ + --hash=sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a \ + --hash=sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677 \ + --hash=sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4 \ + --hash=sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0 \ + --hash=sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58 \ + --hash=sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8 \ + --hash=sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76 \ + --hash=sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9 \ + --hash=sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f \ + --hash=sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11 \ + --hash=sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4 \ + --hash=sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b \ + --hash=sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b \ + --hash=sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76 \ + --hash=sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f \ + --hash=sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d \ + --hash=sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707 + # via + # insightface + # pyautoflip + # scikit-image + # scikit-learn +setuptools==84.0.0 \ + --hash=sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670 \ + --hash=sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73 + # via + # tensorboard + # torch +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via python-dateutil +sounddevice==0.5.5 \ + --hash=sha256:05eb9fd6c54c38d67741441c19164c0dae8ce80453af2d8c4ad2e7823d15b722 \ + --hash=sha256:1234cc9b4c9df97b6cbe748146ae0ec64dd7d6e44739e8e42eaa5b595313a103 \ + --hash=sha256:22487b65198cb5bf2208755105b524f78ad173e5ab6b445bdab1c989f6698df3 \ + --hash=sha256:30ff99f6c107f49d25ad16a45cacd8d91c25a1bcdd3e81a206b921a3a6405b1f \ + --hash=sha256:3861901ddd8230d2e0e8ae62ac320cdd4c688d81df89da036dcb812f757bb3e6 \ + --hash=sha256:cfc6b2c49fb7f555591c78cb8ecf48d6a637fd5b6e1db5fec6ed9365d64b3519 + # via mediapipe +starlette==0.46.2 \ + --hash=sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35 \ + --hash=sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5 + # via fastapi +sympy==1.14.0 \ + --hash=sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517 \ + --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5 + # via torch +tensorboard==2.21.0 \ + --hash=sha256:7279316dcb6bd5bc391d623dea841531299cde1887310e8133bc34a996d32255 + # via pyautoflip +tensorboard-data-server==0.7.2 \ + --hash=sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb \ + --hash=sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60 \ + --hash=sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530 + # via tensorboard +tensorboardx==2.6.5 \ + --hash=sha256:c10b891d00af306537cb8b58a039b2ba41571f0da06f433a41c4ca8d6abe1373 \ + --hash=sha256:ca176db3997ee8c07d2eb77381225956a3fd1c10c91beafab1f17069adc47017 + # via pyautoflip +threadpoolctl==3.6.0 \ + --hash=sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb \ + --hash=sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e + # via scikit-learn +tifffile==2026.7.31 \ + --hash=sha256:79b1f4b1aba3ef3e6b6f1691a32abb62f5d7383faa52a12771c695232ba40bee \ + --hash=sha256:81adfa08012be1c478f99b83cda2f529eef8620cfbdf94fc41eef6f1d7b47dc5 + # via scikit-image +torch==2.13.0+cpu \ + --hash=sha256:0555fde6108ca90247ae33d4e1237cbae475c86a223bb2f0f91d9addf1f611bd \ + --hash=sha256:0b8f7d0423027ae8b90c7977c627f3379f325363a08224dffad9b4b2d684a83d \ + --hash=sha256:10717d8b3b67c45a4788bf7ffc0bab1ea1e5ebbedd24466be6100102d141fac1 \ + --hash=sha256:1a3a35229fdc13446b4eab50e7fcf9399ff941e89a3b761497786297a5d8dde5 \ + --hash=sha256:222a6681467cc7f6f05cd3068dfbc603def3a1e46d1d4620c1c8cdf6178bd563 \ + --hash=sha256:2b3d093abd919ad934c43d47e73ba63ceba7cbd7269fc2e9c1e4fc29e8fe45fa \ + --hash=sha256:3bbb357161e8db43ba7cdcc7e03561eba0c449392f2f27d3566887198fcb4ead \ + --hash=sha256:3fbf9c9d1f3c10c2d59d04aca426dee9ccc6ceb32d255c61e93acc3b4f75fae6 \ + --hash=sha256:4ca4a9394b0c771238a4f73590fdbbc4debad85ed0fa63d026ae1b085da7d6e2 \ + --hash=sha256:6746dbcbeb526eb61330b76b41ff1b4eb848951103a892eeb080dfa2b264667b \ + --hash=sha256:6e9817dbdf5ea76789babd46e457eac5bf14ff566cf85f8addbfdff2d56601ce \ + --hash=sha256:6f307c2c32d764ffc6ff6893b801fad6d4752f3e67966cb8abf1843427c02604 \ + --hash=sha256:7b8d26e29bceafbdaa8d63bfe7612f23875b5af2cc07e13f809c3ed890bbe1d8 \ + --hash=sha256:84453b69508ec79902f899c5ed9495acb9e2bbe9fda5f1d5d6f19e3c3842e1a7 \ + --hash=sha256:8e109528e6bab044815daebaf71770fbaace3a66ef1c816cb55c875350f78a60 \ + --hash=sha256:8eb5002ca81af00ae69b57540f615b58b8ae922b6d4848176b366a52bd2196e6 \ + --hash=sha256:966d020354f465672dc7dd10d3a5c6cd17d7eb48620aa1d265b48a1f78f06898 \ + --hash=sha256:991cc14b39e751122c01f017be6448533989868731cb5eecd1006893d26787c2 \ + --hash=sha256:a17ff48608634db245e17e8bb00a9558554a49aeb1e4f5fe6cd039af2a10515b \ + --hash=sha256:a43376bd094124ef626bfdd3d4c2c62eacb0b5ddc99776f4a32d4fd16f1f3420 \ + --hash=sha256:a8b450c1e58e5800e5b4691dac412f8d2d65a1dc3298166f91596603a3531e6f \ + --hash=sha256:ac7aaf322be4777765a53bed7264a214dd81b3a1d276b93150515a3c5f75e4b0 \ + --hash=sha256:b222c15a0fc2ce207d1c1a59700b46c8fa6748df1f447ad11e5c870dde0933d9 \ + --hash=sha256:ca021f9eb2f8345c83fa03e3a04587308afb8df71bd472670b3ece00df58621c \ + --hash=sha256:d20fa53ee744502fa4c69818a720b05ca0d37abd055d4f6e66cae155114bc691 \ + --hash=sha256:dec241fef3984c0d1edadd1f58708e218d4eae881ceef7bc10cf9964d41b68b9 \ + --hash=sha256:e2e5134decf00e218da62318f3dc5df156231d367871918e91eba95ab0ad43ab \ + --hash=sha256:f028e428bddee95cdb86e2470254e95c9af629362488550c200ed4793125a817 \ + --hash=sha256:f5cbb61180a9793d9e12fe115a2310d2600bd449dfb9a01ec5640e21359fa5ea \ + --hash=sha256:fa0762705b933624d59f6823db9ce7ec2e35b3e1e9c319c9db51fbeecfc3e319 \ + --hash=sha256:ffadde149901c8afa138daa38d898264003cfcf1a3336ca5cd964b5af227d867 + # via + # -r requirements.in + # pyautoflip + # torchvision +torchvision==0.28.0+cpu \ + --hash=sha256:1aa741ae0eb8668b6287dd667548e2dd10179c828db68bfdee1519763b9c5b99 \ + --hash=sha256:1dad604dfc0177ecebe0891bd9701fe2c62ec3f7819a247be541b3fb6effee99 \ + --hash=sha256:22958193d72444ed7cbcc665ba4821a31e5279f9c4d1ad08520918b30896b78a \ + --hash=sha256:2f768c4f6d5adf6d5535061fd69ec44827608bac0e96e12114942a6fdfce1107 \ + --hash=sha256:3a1a76c8decb1d7bbedd3588bccc90fb269944b7321a773db181735b42115422 \ + --hash=sha256:7b6667fd0172463be2a271fb0dbd44b31a7891afd549a66208613ce4cdd79f88 \ + --hash=sha256:7d81da2804da52c9788f2d5a8d0aaddcea9fce6eb5d7c6e19a32b40b4ed0b75a \ + --hash=sha256:82dbffb63d61cd43d9c7a311588e665aa2b21173a05f852b4d384d6782fd88ef \ + --hash=sha256:870d0d42f2eb80f4870cd35e51eea52f596a408a671b28136f06a808846f24c5 \ + --hash=sha256:879ae6d4e2e3651582fb7187eafd535601cb5d019595d47e2c874262a000e88e \ + --hash=sha256:8d8b98608779c770ede5e20609772453ebc7487ebb8697445d1856466c542f45 \ + --hash=sha256:b545d46f4d2f9d30381281cf22874bfe1d32a8a7b0ee8396fccde89f30c6a9d9 \ + --hash=sha256:c6373ec4c2f922e89f45ac91889404d312ba29a31f205b0ad9a725a3894ca246 \ + --hash=sha256:cf5d1c4c355c5b487e7d1c681ef28b9caa2ff0dfa3a32fdcd9e98206a1462bf4 \ + --hash=sha256:d2a0171faa211b506c4dcf3a036942a41077c5d2d3d94883dafbda7b8624a3eb \ + --hash=sha256:d63eae114b4d1fca2b294d300cea3f0d6c71b6d132641e0c4cab1aa06a467b0d \ + --hash=sha256:d88db83abbdfb97199979ec94dd427bc372c1b9ab01f0dbed20af05b0bd644b1 \ + --hash=sha256:fc38699d69d11e563a5b1c02f621f639c4caa9e2ffe6b1ddd4aceedffc0d0578 + # via + # -r requirements.in + # pyautoflip +tqdm==4.70.0 \ + --hash=sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220 \ + --hash=sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953 + # via + # insightface + # pyautoflip + # scenedetect +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 + # via + # anyio + # fastapi + # grpcio + # onnx + # pydantic + # pydantic-core + # torch + # typing-inspection +typing-inspection==0.4.3 \ + --hash=sha256:5f42b23858a91e0b4ef521f5418f03a0da3c9216fd2995ef5e73463100e676cd \ + --hash=sha256:c5f9ec1530b5c1e2c9bc34a84d9a3466ed1b2f3f2fa9f901368d9c5596210e4d + # via pydantic +urllib3==2.7.0 \ + --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ + --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 + # via requests +uvicorn==0.34.2 \ + --hash=sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328 \ + --hash=sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403 + # via -r requirements.in +uvloop==0.22.1 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' \ + --hash=sha256:017bd46f9e7b78e81606329d07141d3da446f8798c6baeec124260e22c262772 \ + --hash=sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e \ + --hash=sha256:05e4b5f86e621cf3927631789999e697e58f0d2d32675b67d9ca9eb0bca55743 \ + --hash=sha256:0ae676de143db2b2f60a9696d7eca5bb9d0dd6cc3ac3dad59a8ae7e95f9e1b54 \ + --hash=sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec \ + --hash=sha256:17d4e97258b0172dfa107b89aa1eeba3016f4b1974ce85ca3ef6a66b35cbf659 \ + --hash=sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8 \ + --hash=sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad \ + --hash=sha256:286322a90bea1f9422a470d5d2ad82d38080be0a29c4dd9b3e6384320a4d11e7 \ + --hash=sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35 \ + --hash=sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289 \ + --hash=sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142 \ + --hash=sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77 \ + --hash=sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733 \ + --hash=sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd \ + --hash=sha256:4a968a72422a097b09042d5fa2c5c590251ad484acf910a651b4b620acd7f193 \ + --hash=sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74 \ + --hash=sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0 \ + --hash=sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6 \ + --hash=sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473 \ + --hash=sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21 \ + --hash=sha256:55502bc2c653ed2e9692e8c55cb95b397d33f9f2911e929dc97c4d6b26d04242 \ + --hash=sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705 \ + --hash=sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702 \ + --hash=sha256:57df59d8b48feb0e613d9b1f5e57b7532e97cbaf0d61f7aa9aa32221e84bc4b6 \ + --hash=sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f \ + --hash=sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e \ + --hash=sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d \ + --hash=sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370 \ + --hash=sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4 \ + --hash=sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792 \ + --hash=sha256:80eee091fe128e425177fbd82f8635769e2f32ec9daf6468286ec57ec0313efa \ + --hash=sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079 \ + --hash=sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2 \ + --hash=sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86 \ + --hash=sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6 \ + --hash=sha256:b45649628d816c030dba3c80f8e2689bab1c89518ed10d426036cdc47874dfc4 \ + --hash=sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3 \ + --hash=sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21 \ + --hash=sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c \ + --hash=sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e \ + --hash=sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25 \ + --hash=sha256:c3e5c6727a57cb6558592a95019e504f605d1c54eb86463ee9f7a2dbd411c820 \ + --hash=sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9 \ + --hash=sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88 \ + --hash=sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2 \ + --hash=sha256:ea721dd3203b809039fcc2983f14608dae82b212288b346e0bfe46ec2fab0b7c \ + --hash=sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c \ + --hash=sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42 + # via uvicorn +watchfiles==1.2.0 \ + --hash=sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9 \ + --hash=sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98 \ + --hash=sha256:027ae72bfdfd254862065d8b3e2a815c6ab9b1853ce41e6648ece84afd34a551 \ + --hash=sha256:03b14855c6f35539e2d95c442ae9530a75762f1e26567152b9ed05f96534a74d \ + --hash=sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7 \ + --hash=sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db \ + --hash=sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69 \ + --hash=sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242 \ + --hash=sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925 \ + --hash=sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f \ + --hash=sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5 \ + --hash=sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5 \ + --hash=sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427 \ + --hash=sha256:11743adfa510bfffebe97659fb280182b5c9b238708f667e866f308c3430dc19 \ + --hash=sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4 \ + --hash=sha256:204f299afcbd65918ab78dbc52626b0ae45e9d8cef403fdbf33ecf9e40eac66e \ + --hash=sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa \ + --hash=sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba \ + --hash=sha256:24b2405c0a46738dd9e1cf7135aa5dbdb9d42d024628651b3b13d5117e99f8df \ + --hash=sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c \ + --hash=sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906 \ + --hash=sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65 \ + --hash=sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c \ + --hash=sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c \ + --hash=sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30 \ + --hash=sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077 \ + --hash=sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374 \ + --hash=sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01 \ + --hash=sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33 \ + --hash=sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831 \ + --hash=sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9 \ + --hash=sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2 \ + --hash=sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b \ + --hash=sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f \ + --hash=sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658 \ + --hash=sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579 \ + --hash=sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5 \ + --hash=sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0 \ + --hash=sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7 \ + --hash=sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666 \ + --hash=sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5 \ + --hash=sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201 \ + --hash=sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103 \ + --hash=sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6 \ + --hash=sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8 \ + --hash=sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1 \ + --hash=sha256:77a0feab9af4c021c581f695258c642b3d10c5fd4c676e33a0d8606425d82631 \ + --hash=sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898 \ + --hash=sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d \ + --hash=sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44 \ + --hash=sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2 \ + --hash=sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5 \ + --hash=sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a \ + --hash=sha256:8c520725602756229f045b032a1ff33d7ef0f7404189d62f6c2438cb6d8ef6a1 \ + --hash=sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b \ + --hash=sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc \ + --hash=sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5 \ + --hash=sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377 \ + --hash=sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8 \ + --hash=sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add \ + --hash=sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281 \ + --hash=sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9 \ + --hash=sha256:a16ffe19bf5cf9f5edaa1ad1dd830c5a816e8feec430c522302ab55483a4b994 \ + --hash=sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0 \ + --hash=sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e \ + --hash=sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0 \ + --hash=sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28 \ + --hash=sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7 \ + --hash=sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55 \ + --hash=sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb \ + --hash=sha256:b62f042afde2dde21ec1d2c1a74361e804673df86f51e418a999c9acfe671b07 \ + --hash=sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb \ + --hash=sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4 \ + --hash=sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0 \ + --hash=sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e \ + --hash=sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4 \ + --hash=sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9 \ + --hash=sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06 \ + --hash=sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26 \ + --hash=sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7 \ + --hash=sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4 \ + --hash=sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3 \ + --hash=sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3 \ + --hash=sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838 \ + --hash=sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71 \ + --hash=sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488 \ + --hash=sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717 \ + --hash=sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d \ + --hash=sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44 \ + --hash=sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2 \ + --hash=sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b \ + --hash=sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2 \ + --hash=sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22 \ + --hash=sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6 \ + --hash=sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e \ + --hash=sha256:e1cfd51e97e13ff3bd047c140764d277fc9b95b7cb5da59e46a47d167adab310 \ + --hash=sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165 \ + --hash=sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5 \ + --hash=sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799 \ + --hash=sha256:eb72919d93e3a16fc451d3aa3d4b1698423daca1b382d3d959c9ac51297c12a8 \ + --hash=sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7 \ + --hash=sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379 \ + --hash=sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925 \ + --hash=sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72 \ + --hash=sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4 \ + --hash=sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08 \ + --hash=sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4 + # via uvicorn +websockets==17.0.1 \ + --hash=sha256:02ed63bf26dda9fa27df730a41f6664586c4ee05972c8fb667ce1725b3fd13d3 \ + --hash=sha256:02f0b037a737d0cb0c33866c97bcd1a0b73170dfbf42d69d8fb86f51002fd5ae \ + --hash=sha256:038cfad5d5417f8bb09295abe986029a26d22f34bda622ccc79b670efd4dab56 \ + --hash=sha256:07abc3bd196a48af476a82fd47f3f79a6a3f70937a9f930cef703cfa0c9d83b6 \ + --hash=sha256:07d78a509c3333f5908c83d7f78144ea68a6c9ec28110f5c54d81d8fcdc262c4 \ + --hash=sha256:0b52c76b8a870b141b7ca0705289452183ce7a523101954ccfe29a25986a673f \ + --hash=sha256:10b1587c599fa0f2c89154587c80e0fda98ade6c9fa8c0260a2823fb1800b685 \ + --hash=sha256:10f461191125c63902ea7394ae9e752b1b5785641850c1d365bb30b0f88bc53f \ + --hash=sha256:15920057a6b723f84734f0641403bca163a4b176e5af809ee4f0c4a1e75e9fed \ + --hash=sha256:17ac37716c0244e82c9e384c41653c090b1864c6610224ca3857e7f7b58fce10 \ + --hash=sha256:18ded646ce98cdd3c0235825b3252f1df55765ba49b616bb10282f758667b4d0 \ + --hash=sha256:1b363bfd72a52c0658a3154a4cff219f15a474b35a235057d38853bf151acce7 \ + --hash=sha256:1bdd8c4be420905dd732e00dcd669852d8128cc723efa585a0c0e51adb00a28a \ + --hash=sha256:1d4cf7e8e5b8b1fa40758ac7524843a00237b124ab217e227542cafcfeb7a946 \ + --hash=sha256:1df81d174c1561292de9e40b141cafc04f69077272f6c352afe1d743e20810df \ + --hash=sha256:20a92f78ac8250984ed459faa9ca48c285adbfc0038ddc3fdac6046990a9c9ed \ + --hash=sha256:22bd00f8bae2bccdb5dbe41e20f58ba44ca9fff0b4b561aaf39099c35da762ed \ + --hash=sha256:2437d4ca208cc0f246d3a2297ae7474b4ba18261aaf5b9c79c84c031ecf348e1 \ + --hash=sha256:246927ae9ae06ca0d42a483a4bdb80d4862e1ee5b4cab37c354a5e1ad8356448 \ + --hash=sha256:2503c7e2a5049a12d5dac917a46d5d52591283a766165b8176bb167560421b38 \ + --hash=sha256:2604de7228506b13a44a256a9d223943340c0e725af5d367dc068e192b027761 \ + --hash=sha256:28012a54510fe8301bb893ef143cec30a2780a2d3bc20b7bbdf4379d7a63945d \ + --hash=sha256:2a855b6dfe21c4d3420be265ae031829ba8ba0be0ea350d9f7c3ef30ae63ebe2 \ + --hash=sha256:2abb1ba0a5133b7d2ef3c1c9f4b0c1e8a101012dce0b594ab2b2888d9a64820e \ + --hash=sha256:2b3f3020171202b135ca078e20434977c6b2b02af647130d6980c9e39b9462e3 \ + --hash=sha256:2bc14b481e05e331811108daa1aeb41a5e237a5564ef2f02ec5a356a0f102f78 \ + --hash=sha256:2fa2cb465a131c347ba6717a78c887746e73edb1c131d01c982d6ef0d68b82e0 \ + --hash=sha256:409d93efcaa14f7a99592c5baaef5ec6ca94fba0f5aec1a86f693977c69c9c1c \ + --hash=sha256:41d6aa06b5ab832aee72fedf47a149535b121ac900b6bb4d3fe14712afac9a79 \ + --hash=sha256:49266e4488309b38783257293a38298942b9a03aa106fcb45195377a77c0c1e2 \ + --hash=sha256:4d1d99db29b5444e3982f1ce2ba8a833508ad44b2f1fbd0bd99e81d825c0b461 \ + --hash=sha256:4d41c0a1d47a478bc432b3b9068097bee1ce0c5b19327ea6f75c2ab34ab1f2fb \ + --hash=sha256:5033ffe6804dd53afafa7d08e8c3eef2d2431f34d58ca30507a8442dd04a033a \ + --hash=sha256:53b90c00bc6201ab6695c7ff51a04d0e425514c37515e9eeecd2c1b978ac6c0e \ + --hash=sha256:54cdcaa56f5d3eafd57058f0fa4a3de93a310b43a3c4699f06efc4c0bd054a5a \ + --hash=sha256:5508f38c98ac29def9e747b87543b008a58b075df6da70b2cf2e0b47073d33bb \ + --hash=sha256:55383d8177b3c99fd873ee5db0e0193f4c1dd4a3feaccf1a4a03c1b7cf539cac \ + --hash=sha256:55b12e47dcee83673a40d07686cfb6f9d6dfc285976ade9463f61d2bef3fad22 \ + --hash=sha256:5661f868ef191d33dfc6a0cc7c5b3d495f0cc8bb3f8b30d87bda8755c61c95f5 \ + --hash=sha256:57d2ee9b24b404ce75f3814f92073c0ed88106c950148d2427fe8d25ca254d1f \ + --hash=sha256:599b03beb77633bffc095334338fad79cafc2b01fbd58953838130a9ae967d7b \ + --hash=sha256:5baa9bc0dfbae8c507e51c8cf1b6d4628086f7a87bbd3a9952bd5f035451f1cc \ + --hash=sha256:5f33a649bfcb8312524173cc4bbafa7dbb236e18eee9aa31a1d324ca0ddda28c \ + --hash=sha256:6740be6d1bab69f08ab52cb15b08f76c143b6fe61c580ba62bd929f3ab7a1d42 \ + --hash=sha256:6a434e59962a4fb9016bea327e1d14d6cd67670ecfb8942b4f4a0c24036634ce \ + --hash=sha256:6db9e5bf3649ab506c6ae8a3ac85a00fb1ae3816d75962771b2df8adbc5d40d2 \ + --hash=sha256:6fd88365da261c53d3e943fb37e0d0721b9cde119f6b2e3fc84369b6ab234d63 \ + --hash=sha256:7002d5f9e1c3ddd991cdfdbfee18cc8c8b196b2445022892badacd6cb338bbbc \ + --hash=sha256:70d438268e49f1a4bd096b6b6f7010f3ab48b5db2574dbf7d8c864c46ce7a06a \ + --hash=sha256:72d7f2a5aeb4e82daa4ee18f125b4277f427033359be5c745ad709608446cc2c \ + --hash=sha256:733e3cc7171fa1b899edbe725ef9382d0e960657dc1fd933f3281ae910c01dab \ + --hash=sha256:734d20364dc2cfe03674883cafcf580b6e431c5ce42b476312b9285310230cf9 \ + --hash=sha256:759adeb5b0c5775b563254ec63b5b79089fc0045b479143a0b1b8c0ebaae1253 \ + --hash=sha256:769ce7e2acfd9a89f2bed3a9c0da229459516bbc00bd4c9e2ca492c613ae4861 \ + --hash=sha256:810cb3fb5fa6e447216f4e82d9a85cb8aed0929ae3538153ddfe8a6e3121a58d \ + --hash=sha256:81ce19c6046ace11da7001781be7317bb1dc389f399af4b2ed962190f76f9add \ + --hash=sha256:846a4a8b0833e3cad57523d9e3bd50ec8ea05ab9d06c582f82a1340ba096af5f \ + --hash=sha256:872273e629ca7e3d35f16a2dc6ede84e1d5c831e616b8277de6e4f83114e7c58 \ + --hash=sha256:8848c207049ad49d318e5f64a3d4d7bb189f8328d0d98e65647788f2a085785c \ + --hash=sha256:884af729b8ab50486acd94d9768c2b60914bf39b579ebba0a5cb73bfdfd61fd2 \ + --hash=sha256:8c07f145d0b9e90cbd96035f31fb79199aef4da1872854e36ebeb258e3d57594 \ + --hash=sha256:8cd3369e42c0246afaf9d669cfc19797e3a49e8c0a639544459c57597108b966 \ + --hash=sha256:8e387adb0c692c6b5571bdeafc8ac9d1901ea30f10309134780b16ecd35e6605 \ + --hash=sha256:90246fa9e6cb192a778ce6ce024057ec54317a894db7899c922dcdc1f4cbf6a5 \ + --hash=sha256:90973a3a00f23afdfd1c9b06fb84289bf0220f247ef8a62501a1967c7af54f7b \ + --hash=sha256:9493314a99e599163c854fb5900ad7f7ea38c5cb9d9103aa30b3c6b8181c01fa \ + --hash=sha256:9f7747d3daa41a11f25f7cca5dc988fc51da97b311bed4c9d843860f79779283 \ + --hash=sha256:a39ce3a7b0e6059be093213d637963101380157bcbad355916738fafb490698d \ + --hash=sha256:a60fa1a25cca1bcc2bf87b8d6be37a741f0a3239fb5e9cfb7a37173b68ffcf87 \ + --hash=sha256:a68e604c6d1b0338e46652e2688cbce8096ad9c03548b075fda9e2ea19a9b7dd \ + --hash=sha256:a8af570fc29cd998a921c7131c8ac81d9434466d6d25300cb12a690fb56a8a08 \ + --hash=sha256:aadc298969ad229d8e3029fc5cc751fdad286696230f9cf014e90ff9cd8e6ea0 \ + --hash=sha256:ab56439c9f74c52770690c7b2f616b3bf775cb3920453ee355ac765c032d8bbf \ + --hash=sha256:ab9f962a5b64a5c3c845d556b7dc4e6fb683f7b67179f8205e814bb2e0213ffe \ + --hash=sha256:afbce6e3f0fac32dc87c2a0d84869d1a706460d64f39f3889386413e6e4d3d26 \ + --hash=sha256:b3ff0ad440ad52dda64138f16895f66403f40192365e39b1010e889f289746b0 \ + --hash=sha256:b580794e926cab7ff42ee4371ef14e0b22cb2bb722a607f77769136468f49a3f \ + --hash=sha256:b85b960a4507b0714c0a1246d031be9118d908ee974dc085257297a955205f1d \ + --hash=sha256:b98860aefbd3d9bc8e3c7f0eefb83b11142b16110739c68cd33d3b4d6e84e536 \ + --hash=sha256:bb31f42ea095ea826463c770829aa188a86c9a5c976b1467cbbf583c811de833 \ + --hash=sha256:bc0bca48ba24c6c866847fd20478a51dd547fa0ad258dab9615c414ec534bbc0 \ + --hash=sha256:bd1470d2c53fe53269bf5619da7725d30dd9b9693f1689f7a85eab8dea734442 \ + --hash=sha256:c09e097d0e46e3c289bedab9a475ae344b70c30ff5646e46af22b4e6fdc97b21 \ + --hash=sha256:c1bec5d6a19f5fbe87e4940739cfc65e7bb53d8b353e1029b8037a1653b321bc \ + --hash=sha256:c1c118a6b0e25bfc9a6802075d748fa6321714ffbdf3c88d29d9a0e3c7386c75 \ + --hash=sha256:c23e532c8a2325a1e7486de8763a60dc43e83f01bcaeca07e3ba79652c156db1 \ + --hash=sha256:c356dbddab0a529ed7574f78f559d75a223735c321c28f6f587fbf02b11ed301 \ + --hash=sha256:c38515cb54902f7e97d0239e81ef46c4444f9475f4807fb9bbdb789b4089abcf \ + --hash=sha256:c395bda8e7d8f51a02e80261fb57127979e5c472675d9a96b2860619ad47da48 \ + --hash=sha256:c6be9cba65c65cc76dfa3d4619e359ff02a4476c74e179b215236c11a0b32345 \ + --hash=sha256:cd526c8228e759c1006c4b7c9ac71dc4e925ced1a6a6a5a8e94643709738f63e \ + --hash=sha256:cddc675ec31bca65473321f9a9794e488b43b3b8de5d02c8ef4810c5d5792163 \ + --hash=sha256:cffc84ddec6da7f447677266fee2a3c40ecc78172f00752aa1150b8a8d65df1d \ + --hash=sha256:d41e9845514754a42d1d83b2fca9d27fee2ca7b3b0bee6843ba5a9bb2b6e25ac \ + --hash=sha256:d69fd559f9f0e8a52d2fce6f04ee143f86e70df0a189cd95164eddac599e810f \ + --hash=sha256:d7d72843691f50b91127c50688df10cb72ec6f4c4b1d7e2c11ab33b16acf8e51 \ + --hash=sha256:d9aac6081513f02eac3f8caace800dbfc5c608b69e4a7bef69e414eabfc95aa1 \ + --hash=sha256:dbfae8e75b342e31fc6fd1a8bbb393b7cbb91d6cfd581650300a94381e7b7e2b \ + --hash=sha256:e8208f2729cba030ff872a92064c97584eeb9502f53d32a05a0f05d5a17ca6c6 \ + --hash=sha256:e95e321d0d763f2b6633512605f6112ebd70d5746f3ce05c941909d4a25233f2 \ + --hash=sha256:e98ec9ec61cce5bc4b8b218322ad090b0994eb060bb04da704c62ef0a3d864e6 \ + --hash=sha256:eab6de8a98b9a7772cf686d00b4de439fc7efb8ab05ae106ef227291d06f87c5 \ + --hash=sha256:efe0ae052a8d023b87198921e8a7ce1dc7768816bcd2fbc20df171ac73a04891 \ + --hash=sha256:f11a398d8170b7ac5000baf7f258dcda579ef3ea744e0cc6a165e0dfbc0d3198 \ + --hash=sha256:f3fd9a1f87f8f0f3f8e9f9bd0195f7516562d13f5b178db8c5784d1f60b60bed \ + --hash=sha256:f47b0815af3948ec6a440b3afa02f05b18cc0939549e91b5c677b5d9c2c8472a \ + --hash=sha256:f991247276797d0c61ab7770bc9791eadc16f683b4d83517f624932adc1a8bab \ + --hash=sha256:ffad64ce7ad3703d652a3fd9af26238377d24ce52c6ad8ff35d26d82f61f493f + # via uvicorn +werkzeug==3.1.8 \ + --hash=sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50 \ + --hash=sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44 + # via tensorboard diff --git a/pyautoflip/warm_models.py b/pyautoflip/warm_models.py new file mode 100644 index 0000000..c630993 --- /dev/null +++ b/pyautoflip/warm_models.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import hashlib +import shutil +import tempfile +import urllib.request +import zipfile +from pathlib import Path + +from insightface.app import FaceAnalysis + + +NAME = "buffalo_s" +URL = "https://github.com/deepinsight/insightface/releases/download/v0.7/buffalo_s.zip" +SHA256 = "d85a87f503f691807cd8bb97128bdf7a0660326cd9cd02657127fa978bab8b5e" +MODEL_DIR = Path.home() / ".insightface" / "models" / NAME + + +def install() -> None: + if MODEL_DIR.is_dir(): + return + MODEL_DIR.parent.mkdir(parents=True, exist_ok=True) + with tempfile.NamedTemporaryFile(suffix=".zip") as archive: + digest = hashlib.sha256() + with urllib.request.urlopen(URL) as response: + while chunk := response.read(1024 * 1024): + archive.write(chunk) + digest.update(chunk) + archive.flush() + actual = digest.hexdigest() + if actual != SHA256: + raise RuntimeError(f"{NAME} checksum mismatch: {actual}") + temporary = Path(tempfile.mkdtemp(dir=MODEL_DIR.parent)) + try: + with zipfile.ZipFile(archive.name) as bundle: + bundle.extractall(temporary) + temporary.rename(MODEL_DIR) + except BaseException: + shutil.rmtree(temporary, ignore_errors=True) + raise + + +install() +app = FaceAnalysis(name=NAME, providers=["CPUExecutionProvider"]) +app.prepare(ctx_id=-1, det_size=(640, 640)) +print(f"insightface {NAME} ready ({SHA256})") diff --git a/scripts/bootstrap-n8n.sh b/scripts/bootstrap-n8n.sh deleted file mode 100755 index a8fb13d..0000000 --- a/scripts/bootstrap-n8n.sh +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env bash -# Import syndicator credentials + workflows into the running compose n8n, -# publish webhook workflows, and smoke-check webhooks + pyautoflip. -set -euo pipefail - -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -cd "$ROOT" - -if [[ ! -f .env ]]; then - echo "Missing .env — copy .env.example and fill secrets." >&2 - exit 1 -fi - -# shellcheck disable=SC1091 -set -a -# shellcheck source=/dev/null -source .env -set +a - -COMPOSE=(docker compose) -N8N_EXEC=(docker compose exec -T -u node n8n) -# Import order: leaves before parents (same as publish). -SYNDICATOR_WORKFLOWS=( - "Adapt Hugo Media" - "Adapt Feature Image" - "Adapt Reel Media" - "Blog Post Publish" - "Reel Publish" -) -# Publish order: n8n 2.x requires referenced sub-workflows to be published -# before parents that call them. -PUBLISH_WORKFLOW_IDS=( - "OGa6Xa8GxkSmA7Cr" # Adapt Hugo Media - "8NOGn9jgOoV0fw0u" # Adapt Feature Image - "y9TTx7N8Iygn88ry" # Adapt Reel Media - "l7HCCWtO1ALC82n6" # Blog Post Publish - "zh21miLsQC8Jvua6" # Reel Publish -) - -need() { - local name="$1" - if [[ -z "${!name:-}" ]]; then - echo "Missing required env: $name" >&2 - exit 1 - fi -} - -need N8N_ENCRYPTION_KEY -need N8N_OWNER_EMAIL -need N8N_OWNER_PASSWORD -need OPENAI_API_KEY -need POSTIZ_API_KEY -need SFTP_HOST -need SFTP_USERNAME - -# Create n8n↔sftp keypair if missing, and refresh authorized public key. -"$ROOT/scripts/ensure-sftp-keys.sh" -# Bcrypt owner password into secrets/n8n_owner.env for Compose. -"$ROOT/scripts/ensure-n8n-owner.sh" - -if [[ -z "${SFTP_PRIVATE_KEY:-}" ]]; then - key_file="${SFTP_PRIVATE_KEY_FILE:-./secrets/sftp_n8n_ed25519}" - if [[ ! -f "$key_file" ]]; then - echo "Set SFTP_PRIVATE_KEY or provide $key_file" >&2 - exit 1 - fi - SFTP_PRIVATE_KEY="$(cat "$key_file")" - export SFTP_PRIVATE_KEY -fi - -# Restart sftp so sshd-wrapper re-syncs authorized_keys from sftp/keys/. -if [[ -n "$("${COMPOSE[@]}" ps -q sftp 2>/dev/null || true)" ]]; then - echo "Restarting sftp to pick up authorized keys…" - "${COMPOSE[@]}" restart sftp -fi -for _ in $(seq 1 30); do - if "${COMPOSE[@]}" exec -T sftp pgrep -x sshd >/dev/null 2>&1; then - break - fi - sleep 1 -done - -echo "Waiting for n8n…" -for _ in $(seq 1 60); do - if "${COMPOSE[@]}" exec -T n8n wget -qO- http://127.0.0.1:5678/healthz >/dev/null 2>&1; then - break - fi - sleep 2 -done -if ! "${COMPOSE[@]}" exec -T n8n wget -qO- http://127.0.0.1:5678/healthz >/dev/null 2>&1; then - echo "n8n did not become healthy" >&2 - exit 1 -fi - -N8N_BASE="http://127.0.0.1:${N8N_HOST_PORT:-5678}" -API_KEY_LABEL="syndicator-bootstrap" -API_KEY_FILE="$ROOT/secrets/n8n_api_key" -TMP_DIR="$(mktemp -d)" -COOKIE_JAR="$TMP_DIR/n8n-cookies.txt" -trap 'rm -rf "$TMP_DIR"' EXIT - -ensure_n8n_api_key() { - if [[ -n "${N8N_API_KEY:-}" ]]; then - echo "Using N8N_API_KEY from environment" - return - fi - if [[ -f "$API_KEY_FILE" && -s "$API_KEY_FILE" ]]; then - N8N_API_KEY="$(tr -d '[:space:]' <"$API_KEY_FILE")" - if [[ -n "$N8N_API_KEY" ]]; then - echo "Using API key from $API_KEY_FILE" - export N8N_API_KEY - return - fi - fi - - echo "Logging into n8n to provision API key…" - local login_code - login_code="$(curl -sS -o /tmp/n8n-login-body -w '%{http_code}' -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ - -X POST \ - -H 'Content-Type: application/json' \ - -d "$(python3 -c 'import json,os; print(json.dumps({"emailOrLdapLoginId":os.environ["N8N_OWNER_EMAIL"],"password":os.environ["N8N_OWNER_PASSWORD"]}))')" \ - "${N8N_BASE}/rest/login" || true)" - if [[ "$login_code" != "200" ]]; then - echo "n8n login failed (HTTP $login_code): $(cat /tmp/n8n-login-body)" >&2 - echo "Ensure N8N_OWNER_EMAIL/PASSWORD match the env-managed owner and that ensure-n8n-owner.sh ran before compose up." >&2 - exit 1 - fi - - local scopes_json key_id raw_key create_body - scopes_json="$(curl -sS -c "$COOKIE_JAR" -b "$COOKIE_JAR" "${N8N_BASE}/rest/api-keys/scopes")" - scopes_json="$(python3 -c ' -import json,sys -body=json.load(sys.stdin) -scopes=body.get("data", body) -if not isinstance(scopes, list): - raise SystemExit(f"Unexpected scopes response: {body!r}") -print(json.dumps(scopes)) -' <<<"$scopes_json")" - - key_id="$(curl -sS -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ - --get \ - --data-urlencode "label=${API_KEY_LABEL}" \ - --data-urlencode "ownership=mine" \ - --data-urlencode "take=50" \ - "${N8N_BASE}/rest/api-keys" | python3 -c ' -import json,sys -body=json.load(sys.stdin) -payload=body.get("data", body) -if isinstance(payload, dict): - items=payload.get("items", payload.get("data", [])) -elif isinstance(payload, list): - items=payload -else: - items=[] -label=sys.argv[1] -for item in items or []: - if item.get("label")==label: - print(item.get("id","")) - break -' "$API_KEY_LABEL")" - - if [[ -n "$key_id" ]]; then - echo "Rotating API key label=$API_KEY_LABEL id=$key_id…" - raw_key="$(curl -sS -c "$COOKIE_JAR" -b "$COOKIE_JAR" -X POST \ - "${N8N_BASE}/rest/api-keys/${key_id}/rotate" | python3 -c ' -import json,sys -body=json.load(sys.stdin) -data=body.get("data", body) -key=data.get("rawApiKey") or data.get("apiKey") or "" -if not key or key.startswith("*"): - raise SystemExit(f"Rotate did not return rawApiKey: {body!r}") -print(key) -')" - else - echo "Creating API key label=$API_KEY_LABEL…" - create_body="$(python3 -c ' -import json,sys -scopes=json.loads(sys.argv[1]) -print(json.dumps({"label":sys.argv[2],"expiresAt":None,"scopes":scopes})) -' "$scopes_json" "$API_KEY_LABEL")" - raw_key="$(curl -sS -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ - -X POST \ - -H 'Content-Type: application/json' \ - -d "$create_body" \ - "${N8N_BASE}/rest/api-keys" | python3 -c ' -import json,sys -body=json.load(sys.stdin) -data=body.get("data", body) -key=data.get("rawApiKey") or "" -if not key: - raise SystemExit(f"Create did not return rawApiKey: {body!r}") -print(key) -')" - fi - - mkdir -p "$(dirname "$API_KEY_FILE")" - umask 077 - printf '%s\n' "$raw_key" >"$API_KEY_FILE" - chmod 600 "$API_KEY_FILE" - N8N_API_KEY="$raw_key" - export N8N_API_KEY - echo "Wrote $API_KEY_FILE" -} - -ensure_n8n_api_key - -resolve_owner_user_id() { - if [[ -n "${N8N_OWNER_USER_ID:-}" ]]; then - printf '%s' "$N8N_OWNER_USER_ID" - return - fi - local vol - # compose `name: syndicator` → volume syndicator_n8n_data - vol="syndicator_n8n_data" - if ! docker volume inspect "$vol" >/dev/null 2>&1; then - vol="$(docker volume ls -q | grep -E 'n8n_data$' | head -1 || true)" - fi - if [[ -n "$vol" ]]; then - docker run --rm -v "${vol}:/data:ro" alpine sh -c \ - 'apk add --no-cache sqlite >/dev/null && sqlite3 /data/database.sqlite "SELECT id FROM user WHERE roleSlug='\''global:owner'\'' LIMIT 1;"' \ - 2>/dev/null || true - fi -} - -OWNER_USER_ID="$(resolve_owner_user_id | tr -d '[:space:]')" -if [[ -z "$OWNER_USER_ID" ]]; then - echo "Could not resolve n8n owner user id. Ensure N8N_INSTANCE_OWNER_* is configured (./scripts/ensure-n8n-owner.sh + compose up), then re-run (or set N8N_OWNER_USER_ID)." >&2 - exit 1 -fi -echo "Using owner userId=$OWNER_USER_ID" - -render_credential() { - local template="$1" - local out="$2" - python3 - "$template" "$out" <<'PY' -import json, os, re, sys -src, dst = sys.argv[1], sys.argv[2] -raw = open(src, encoding="utf-8").read() - -def repl(match: re.Match[str]) -> str: - key = match.group(1) - if key not in os.environ: - raise SystemExit(f"Missing env for template: {key}") - return json.dumps(os.environ[key])[1:-1] # escape for JSON string context - -# Replace ${VAR} inside JSON string values with JSON-escaped content. -rendered = re.sub(r"\$\{([A-Z0-9_]+)\}", repl, raw) -json.loads(rendered) # validate -open(dst, "w", encoding="utf-8").write(rendered) -PY -} - -echo "Importing credentials…" -for template in n8n/credentials/*.template.json; do - base="$(basename "$template" .template.json)" - rendered="$TMP_DIR/${base}.json" - render_credential "$template" "$rendered" - # Copy into container and import (decrypted JSON never stays in the repo). - docker compose cp "$rendered" "n8n:/tmp/${base}.json" - "${N8N_EXEC[@]}" n8n import:credentials --input="/tmp/${base}.json" --userId="$OWNER_USER_ID" - "${N8N_EXEC[@]}" rm -f "/tmp/${base}.json" -done - -echo "Importing workflows…" -for name in "${SYNDICATOR_WORKFLOWS[@]}"; do - file="n8n/workflows/${name}.json" - if [[ ! -f "$file" ]]; then - echo "Missing workflow export: $file" >&2 - exit 1 - fi - docker compose cp "$file" "n8n:/tmp/workflow-import.json" - "${N8N_EXEC[@]}" n8n import:workflow --input="/tmp/workflow-import.json" --userId="$OWNER_USER_ID" - "${N8N_EXEC[@]}" rm -f /tmp/workflow-import.json -done - -publish_workflow() { - local id="$1" - local code - code="$(curl -sS -o /tmp/n8n-activate-body -w '%{http_code}' -X POST \ - -H "X-N8N-API-KEY: ${N8N_API_KEY}" \ - -H "Content-Type: application/json" \ - "http://127.0.0.1:${N8N_HOST_PORT:-5678}/api/v1/workflows/${id}/publish" || true)" - if [[ "$code" != "200" ]]; then - # Fallback for older n8n builds - code="$(curl -sS -o /tmp/n8n-activate-body -w '%{http_code}' -X POST \ - -H "X-N8N-API-KEY: ${N8N_API_KEY}" \ - -H "Content-Type: application/json" \ - "http://127.0.0.1:${N8N_HOST_PORT:-5678}/api/v1/workflows/${id}/activate" || true)" - fi - if [[ "$code" != "200" ]]; then - echo "Failed to publish workflow $id (HTTP $code): $(cat /tmp/n8n-activate-body)" >&2 - exit 1 - fi - echo " published $id" -} - -echo "Publishing workflows (sub-workflows before webhook parents)…" -for id in "${PUBLISH_WORKFLOW_IDS[@]}"; do - publish_workflow "$id" -done - -echo "Verifying production webhooks…" -for path in /webhook/publish /webhook/reel; do - code="$(curl -sS -o /dev/null -w '%{http_code}' -X POST \ - -H 'Content-Type: application/json' \ - -d '{}' \ - "http://127.0.0.1:${N8N_HOST_PORT:-5678}${path}" || true)" - if [[ "$code" == "404" ]]; then - echo "Webhook $path returned 404 — workflow likely inactive or path mismatch" >&2 - exit 1 - fi - echo " $path → HTTP $code" -done - -echo "Verifying pyautoflip from n8n network…" -health="$("${COMPOSE[@]}" exec -T n8n wget -qO- http://pyautoflip:8080/health || true)" -if [[ "$health" != *'"status":"ok"'* && "$health" != *'"status": "ok"'* ]]; then - echo "pyautoflip /health failed: $health" >&2 - exit 1 -fi -echo " pyautoflip /health → $health" - -echo "Bootstrap complete." diff --git a/scripts/doctor.sh b/scripts/doctor.sh new file mode 100755 index 0000000..85482e8 --- /dev/null +++ b/scripts/doctor.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +# shellcheck source=scripts/lib.sh +source "$ROOT/scripts/lib.sh" + +require_config=0 +if [[ "${1:-}" == "--require-config" ]]; then + require_config=1 +elif [[ "$#" -gt 0 ]]; then + echo "Usage: $0 [--require-config]" >&2 + exit 2 +fi + +failed=0 +for command in docker curl python3 openssl ssh-keygen sftp; do + if ! command -v "$command" >/dev/null 2>&1; then + echo "Missing required command: $command" >&2 + failed=1 + fi +done + +if [[ "$failed" -ne 0 ]]; then + exit 1 +fi + +if ! docker info >/dev/null 2>&1; then + echo "Docker is installed but its daemon is unavailable." >&2 + exit 1 +fi +docker compose version >/dev/null + +if [[ "$require_config" -eq 0 ]]; then + echo "Host prerequisites are available." + exit 0 +fi + +if [[ ! -f "$ENV_FILE" ]]; then + echo "Missing environment file: $ENV_FILE" >&2 + exit 1 +fi +compose config --quiet + +echo "Host and configuration are valid." diff --git a/scripts/ensure-n8n-owner.sh b/scripts/ensure-n8n-owner.sh deleted file mode 100755 index 6080e00..0000000 --- a/scripts/ensure-n8n-owner.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash -# Idempotently bcrypt-hash N8N_OWNER_PASSWORD into secrets/n8n_owner.env for Compose. -# Run before `docker compose up` (bootstrap also runs this). -set -euo pipefail - -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -cd "$ROOT" - -if [[ ! -f .env ]]; then - echo "Missing .env — copy .env.example and fill N8N_OWNER_EMAIL / N8N_OWNER_PASSWORD." >&2 - exit 1 -fi - -# shellcheck disable=SC1091 -set -a -# shellcheck source=/dev/null -source .env -set +a - -need() { - local name="$1" - if [[ -z "${!name:-}" ]]; then - echo "Missing required env: $name" >&2 - exit 1 - fi -} - -need N8N_OWNER_EMAIL -need N8N_OWNER_PASSWORD - -mkdir -p "$ROOT/secrets" -out="$ROOT/secrets/n8n_owner.env" - -hash_password() { - # Prefer host bcrypt; fall back to a one-shot container (Docker is required anyway). - if python3 -c 'import bcrypt' >/dev/null 2>&1; then - N8N_OWNER_PASSWORD="$N8N_OWNER_PASSWORD" python3 - <<'PY' -import bcrypt, os -password = os.environ["N8N_OWNER_PASSWORD"].encode() -print(bcrypt.hashpw(password, bcrypt.gensalt(rounds=10)).decode()) -PY - return - fi - printf '%s' "$N8N_OWNER_PASSWORD" | docker run --rm -i python:3.12-alpine sh -c ' - pip install -q bcrypt >/dev/null - python -c "import bcrypt,sys; p=sys.stdin.buffer.read(); print(bcrypt.hashpw(p, bcrypt.gensalt(rounds=10)).decode())" - ' -} - -echo "Hashing n8n owner password…" -raw_hash="$(hash_password)" -if [[ ! "$raw_hash" =~ ^\$2[aby]\$ ]]; then - echo "Expected a bcrypt hash, got: ${raw_hash:0:20}…" >&2 - exit 1 -fi - -# Compose env_file interpolates $VAR; escape each $ as $$ for a literal hash. -escaped_hash="${raw_hash//\$/\$\$}" - -umask 077 -cat >"$out" <"$pub_file" -chmod 644 "$pub_file" -# ssh-keygen also writes key_file.pub on create; keep a single canonical pubkey path. -rm -f "${key_file}.pub" -echo "Authorized public key: $pub_file" diff --git a/scripts/export-workflows.sh b/scripts/export-workflows.sh index 53a0e98..ad4e5eb 100755 --- a/scripts/export-workflows.sh +++ b/scripts/export-workflows.sh @@ -4,33 +4,26 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -cd "$ROOT" +# shellcheck source=scripts/lib.sh +source "$ROOT/scripts/lib.sh" OUT_DIR="$ROOT/n8n/workflows" mkdir -p "$OUT_DIR" -# id|filename (without .json) -WORKFLOWS=( - "l7HCCWtO1ALC82n6|Blog Post Publish" - "zh21miLsQC8Jvua6|Reel Publish" - "OGa6Xa8GxkSmA7Cr|Adapt Hugo Media" - "8NOGn9jgOoV0fw0u|Adapt Feature Image" - "y9TTx7N8Iygn88ry|Adapt Reel Media" -) - TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT -for entry in "${WORKFLOWS[@]}"; do - id="${entry%%|*}" - name="${entry#*|}" +count=0 +for source in "$OUT_DIR"/*.json; do + name="$(basename "$source" .json)" + id="$(workflow_id "$source")" echo "Exporting $name ($id)…" - docker compose exec -T -u node n8n \ + compose exec -T -u node n8n \ n8n export:workflow --id="$id" --pretty --output="/tmp/export-${id}.json" - docker compose cp "n8n:/tmp/export-${id}.json" "$TMP/${name}.json" - docker compose exec -T -u node n8n rm -f "/tmp/export-${id}.json" + compose cp "n8n:/tmp/export-${id}.json" "$TMP/${name}.json" + compose exec -T -u node n8n rm -f "/tmp/export-${id}.json" - # n8n may wrap a single workflow in an array — normalize to one object file. + # Normalize a single workflow and remove instance-specific export metadata. python3 - "$TMP/${name}.json" "$OUT_DIR/${name}.json" <<'PY' import json, sys src, dst = sys.argv[1], sys.argv[2] @@ -39,9 +32,15 @@ if isinstance(data, list): if len(data) != 1: raise SystemExit(f"Expected 1 workflow in {src}, got {len(data)}") data = data[0] +data["pinData"] = {} +data.pop("shared", None) +data.pop("versionMetadata", None) +if isinstance(data.get("meta"), dict): + data["meta"].pop("instanceId", None) json.dump(data, open(dst, "w", encoding="utf-8"), indent=2, ensure_ascii=False) open(dst, "a", encoding="utf-8").write("\n") PY + count=$((count + 1)) done -echo "Wrote ${#WORKFLOWS[@]} workflows to $OUT_DIR" +echo "Wrote $count workflows to $OUT_DIR" diff --git a/scripts/init.sh b/scripts/init.sh new file mode 100755 index 0000000..d4c7cad --- /dev/null +++ b/scripts/init.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +# shellcheck source=scripts/lib.sh +source "$ROOT/scripts/lib.sh" + +created=0 +if [[ ! -f "$ENV_FILE" ]]; then + mkdir -p "$(dirname "$ENV_FILE")" + cp "$ROOT/.env.example" "$ENV_FILE" + created=1 +fi +chmod 600 "$ENV_FILE" + +status=0 +python3 - "$ENV_FILE" <<'PY' || status=$? +from pathlib import Path +import secrets +import sys + +path = Path(sys.argv[1]) +required = ( + "N8N_ENCRYPTION_KEY", + "N8N_OWNER_EMAIL", + "N8N_OWNER_PASSWORD", + "OPENAI_API_KEY", + "POSTIZ_API_KEY", +) +values: dict[str, str] = {} +lines = path.read_text(encoding="utf-8").splitlines() +key_index = None +for index, line in enumerate(lines): + stripped = line.strip() + if not stripped or stripped.startswith("#") or "=" not in line: + continue + name, value = line.split("=", 1) + name = name.strip() + values[name] = value + if name == "N8N_ENCRYPTION_KEY": + key_index = index + +if not values.get("N8N_ENCRYPTION_KEY"): + generated = secrets.token_hex(32) + replacement = f"N8N_ENCRYPTION_KEY={generated}" + if key_index is None: + lines.append(replacement) + else: + lines[key_index] = replacement + path.write_text("\n".join(lines) + "\n", encoding="utf-8") + values["N8N_ENCRYPTION_KEY"] = generated + print(f"Generated N8N_ENCRYPTION_KEY in {path}") + +missing = [name for name in required if not values.get(name)] +if missing: + for name in missing: + print(f"Missing required environment value: {name}", file=sys.stderr) + raise SystemExit(2) +PY +if [[ "$status" -eq 2 ]]; then + if [[ "$created" -eq 1 ]]; then + echo "Created $ENV_FILE. Fill the remaining values, then run init again." >&2 + fi + exit 2 +elif [[ "$status" -ne 0 ]]; then + exit "$status" +fi + +echo "Initialization is complete." diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100644 index 0000000..0765a03 --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SOURCE_ROOT="${SYNDICATOR_SOURCE_ROOT:-$ROOT}" +ENV_FILE="${SYNDICATOR_ENV_FILE:-$ROOT/.env}" + +cd "$ROOT" || exit 1 + +resolve_from_root() { + local path="$1" + if [[ "$path" = /* ]]; then + printf '%s\n' "$path" + else + printf '%s\n' "$ROOT/${path#./}" + fi +} + +compose() { + local env_file="${SYNDICATOR_ENV_FILE:-$ENV_FILE}" + local args=( + --project-directory "$SOURCE_ROOT" + -f "$SOURCE_ROOT/docker-compose.yml" + ) + if [[ -f "$env_file" ]]; then + args+=(--env-file "$env_file") + fi + if [[ -n "${SYNDICATOR_PROJECT:-}" ]]; then + args+=(-p "$SYNDICATOR_PROJECT") + fi + docker compose "${args[@]}" "$@" +} + +wait_for_n8n() { + local attempts="${1:-60}" + local delay="${2:-2}" + local count + echo "Waiting for n8n..." + for ((count = 1; count <= attempts; count++)); do + if compose exec -T n8n wget -qO- \ + http://127.0.0.1:5678/healthz/readiness >/dev/null 2>&1; then + return 0 + fi + sleep "$delay" + done + echo "n8n did not become healthy" >&2 + return 1 +} + +run_reconcile() { + compose --profile reconcile run --rm -T n8n-reconcile +} + +workflow_id() { + python3 - "$1" <<'PY' +import json +import sys + +with open(sys.argv[1], encoding="utf-8") as handle: + print(json.load(handle)["id"]) +PY +} diff --git a/scripts/update.sh b/scripts/update.sh deleted file mode 100755 index 6780b55..0000000 --- a/scripts/update.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -# Rebuild n8n + pyautoflip from the latest base images and recreate when changed. -# Safe to re-run (volumes untouched). Intended for cron / systemd timer. -set -euo pipefail - -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -cd "$ROOT" - -LOG="${UPDATE_LOG:-$ROOT/update.log}" -mkdir -p "$(dirname "$LOG")" -exec >>"$LOG" 2>&1 - -echo "=== $(date -Is) starting syndicator update ===" - -n8n_old="$(docker image inspect syndicator-n8n:stable --format '{{.Id}}' 2>/dev/null || true)" -py_old="$(docker image inspect syndicator-pyautoflip:local --format '{{.Id}}' 2>/dev/null || true)" - -docker compose build --pull -docker compose up -d --remove-orphans - -n8n_new="$(docker image inspect syndicator-n8n:stable --format '{{.Id}}' 2>/dev/null || true)" -py_new="$(docker image inspect syndicator-pyautoflip:local --format '{{.Id}}' 2>/dev/null || true)" - -changed=0 -if [[ "$n8n_old" != "$n8n_new" ]]; then - echo "Updated syndicator-n8n:stable" - echo " old: ${n8n_old:-}" - echo " new: $n8n_new" - changed=1 -else - echo "n8n image unchanged (${n8n_new:-})" -fi - -if [[ "$py_old" != "$py_new" ]]; then - echo "Updated syndicator-pyautoflip:local" - echo " old: ${py_old:-}" - echo " new: $py_new" - changed=1 -else - echo "pyautoflip image unchanged (${py_new:-})" -fi - -if [[ "$changed" -eq 1 ]]; then - docker image prune -f >/dev/null -fi - -echo "=== $(date -Is) done ===" diff --git a/scripts/verify.sh b/scripts/verify.sh new file mode 100755 index 0000000..5cac0da --- /dev/null +++ b/scripts/verify.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +# shellcheck source=scripts/lib.sh +source "$ROOT/scripts/lib.sh" + +wait_for_n8n 30 2 +run_reconcile + +echo "n8n health and workflow publication are valid." + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +n8n_published="$(compose port n8n 5678 | head -n1)" +n8n_url="http://${n8n_published}" +mapfile -t webhook_paths < <( + python3 - "$ROOT/n8n/workflows" <<'PY' +import json +import pathlib +import sys + +root = pathlib.Path(sys.argv[1]) +for path in sorted(root.glob("*.json")): + workflow = json.loads(path.read_text(encoding="utf-8")) + for node in workflow.get("nodes") or []: + if node.get("type") != "n8n-nodes-base.webhook": + continue + hook = str((node.get("parameters") or {}).get("path") or "").strip().strip("/") + if hook: + print(hook) +PY +) +if [[ "${#webhook_paths[@]}" -eq 0 ]]; then + echo "No webhook paths found in n8n/workflows." >&2 + exit 1 +fi +for path in "${webhook_paths[@]}"; do + # GET a POST-only production webhook. Live: "Did you mean to make a POST request?" + # Missing from the live router: "The requested webhook ... is not registered." + code="$(curl -sS -o "$tmp/webhook-${path}.txt" -w '%{http_code}' \ + "${n8n_url}/webhook/${path}" || true)" + body="$(<"$tmp/webhook-${path}.txt")" + if [[ "$code" == "405" ]] || + [[ "$body" == *"Did you mean to make a POST request"* ]] || + [[ "$body" == *"not registered for GET"* ]]; then + continue + fi + echo "Webhook /webhook/${path} is not registered (HTTP ${code}): ${body}" >&2 + exit 1 +done +echo "Publish and reel webhooks are registered." + +health="$(compose exec -T n8n wget -qO- http://pyautoflip:8080/health || true)" +if [[ "$health" != *'"status":"ok"'* && "$health" != *'"status": "ok"'* ]]; then + echo "pyautoflip health check failed: $health" >&2 + exit 1 +fi +echo "pyautoflip is reachable from n8n." + +client_key="$(resolve_from_root "${SFTP_CLIENT_KEY_FILE:-secrets/sftp_client_ed25519}")" +if [[ ! -f "$client_key" ]]; then + echo "Skipping SFTP check; no client key at $client_key." +else + published="$(compose port sftp 22)" + sftp_port="${published##*:}" + ssh-keyscan -p "$sftp_port" 127.0.0.1 >"$tmp/known_hosts" 2>/dev/null + printf 'pwd\nquit\n' | sftp -q -b - \ + -P "$sftp_port" \ + -i "$client_key" \ + -o BatchMode=yes \ + -o IdentitiesOnly=yes \ + -o StrictHostKeyChecking=yes \ + -o "UserKnownHostsFile=$tmp/known_hosts" \ + "sftp@127.0.0.1" >/dev/null + echo "SFTP key authentication is valid." +fi + +echo "Syndicator verification complete." diff --git a/scripts/workflow_order.py b/scripts/workflow_order.py new file mode 100755 index 0000000..ae47c6d --- /dev/null +++ b/scripts/workflow_order.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""List workflow files with referenced sub-workflows before their parents.""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Any + + +def reference(node: dict[str, Any]) -> str | None: + value = node.get("parameters", {}).get("workflowId") + if isinstance(value, str): + return value + if isinstance(value, dict) and isinstance(value.get("value"), str): + return value["value"] + return None + + +def main() -> int: + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} SOURCE_ROOT", file=sys.stderr) + return 2 + source_root = Path(sys.argv[1]) + paths = sorted((source_root / "n8n" / "workflows").glob("*.json")) + workflows = {path: json.loads(path.read_text(encoding="utf-8")) for path in paths} + by_id = {workflow["id"]: path for path, workflow in workflows.items()} + + ordered: list[Path] = [] + visiting: set[Path] = set() + visited: set[Path] = set() + + def visit(path: Path) -> None: + if path in visited: + return + if path in visiting: + raise ValueError(f"workflow dependency cycle at {path.name}") + visiting.add(path) + dependencies = { + dependency + for node in workflows[path].get("nodes", []) + if (dependency := reference(node)) is not None + } + for dependency in sorted(dependencies): + if dependency not in by_id: + raise ValueError(f"{path.name} references unknown workflow {dependency}") + visit(by_id[dependency]) + visiting.remove(path) + visited.add(path) + ordered.append(path) + + for path in paths: + visit(path) + for path in ordered: + print(path.relative_to(source_root)) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (OSError, KeyError, ValueError, json.JSONDecodeError) as exc: + print(exc, file=sys.stderr) + raise SystemExit(1) from exc diff --git a/sftp/Dockerfile b/sftp/Dockerfile deleted file mode 100644 index 2fc592b..0000000 --- a/sftp/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -# Thin wrapper around atmoz/sftp: durable host keys, key sync, data chown. -FROM atmoz/sftp:alpine - -# atmoz execs /usr/sbin/sshd by absolute path — replace it with our wrapper. -RUN mv /usr/sbin/sshd /usr/sbin/sshd.real -COPY sshd-wrapper.sh /usr/sbin/sshd -COPY entrypoint.sh /syndicator-entrypoint.sh -RUN chmod +x /usr/sbin/sshd /syndicator-entrypoint.sh - -ENTRYPOINT ["/syndicator-entrypoint.sh"] diff --git a/sftp/authorized_keys.example b/sftp/authorized_keys.example index 08e4055..34ce5e3 100644 --- a/sftp/authorized_keys.example +++ b/sftp/authorized_keys.example @@ -1,3 +1,3 @@ # Example OpenSSH public key (replace with a real key before compose up). -# Copy to this directory as e.g. mac.pub or n8n.pub — filenames are arbitrary. +# Copy to this directory as e.g. mac.pub or laptop.pub — filenames are arbitrary. ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleReplaceWithRealPublicKey syndicator-example diff --git a/sftp/entrypoint.sh b/sftp/entrypoint.sh deleted file mode 100755 index 7f3ddc5..0000000 --- a/sftp/entrypoint.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Wrapper around atmoz/sftp entrypoint: -# - durable host keys in a named volume (no host-side keygen / bind mounts) -# - real sshd is replaced at build time by sshd-wrapper (see Dockerfile) -set -Eeo pipefail - -HOST_KEY_DIR="${SFTP_HOST_KEY_DIR:-/etc/ssh/host_keys}" - -mkdir -p "$HOST_KEY_DIR" - -if [[ ! -f "$HOST_KEY_DIR/ssh_host_ed25519_key" ]]; then - echo "[syndicator-sftp] generating ed25519 host key" - ssh-keygen -t ed25519 -f "$HOST_KEY_DIR/ssh_host_ed25519_key" -N '' /dev/null || true -# Install into /etc/ssh (image layer is writable; avoids 4 bind mounts). -cp -a "$HOST_KEY_DIR"/ssh_host_ed25519_key "$HOST_KEY_DIR"/ssh_host_ed25519_key.pub \ - "$HOST_KEY_DIR"/ssh_host_rsa_key "$HOST_KEY_DIR"/ssh_host_rsa_key.pub \ - /etc/ssh/ -chmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key - -exec /entrypoint "$@" diff --git a/sftp/setup.sh b/sftp/setup.sh new file mode 100755 index 0000000..d6c9375 --- /dev/null +++ b/sftp/setup.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Supported atmoz/sftp startup hook: persist host keys, refresh user keys, and +# repair the writable data directory without replacing the sshd binary. +set -Eeo pipefail + +HOST_KEY_DIR="${SFTP_HOST_KEY_DIR:-/etc/ssh/host_keys}" +SFTP_UID="${SFTP_UID:-1000}" +SFTP_GID="${SFTP_GID:-1000}" +DATA_DIR="${SFTP_DATA_DIR:-/home/sftp/syndicator}" + +mkdir -p "$HOST_KEY_DIR" + +if [[ ! -f "$HOST_KEY_DIR/ssh_host_ed25519_key" ]]; then + ssh-keygen -t ed25519 -f "$HOST_KEY_DIR/ssh_host_ed25519_key" -N '' /dev/null; then + sort -u "$keys_dir"/* >"$auth" + else + : >"$auth" + fi + chown "$(id -u "$user")" "$auth" + chmod 600 "$auth" +done + +if [[ -d "$DATA_DIR" ]]; then + chown "${SFTP_UID}:${SFTP_GID}" "$DATA_DIR" + chmod 755 "$DATA_DIR" +fi diff --git a/sftp/sshd-wrapper.sh b/sftp/sshd-wrapper.sh deleted file mode 100755 index e299cd0..0000000 --- a/sftp/sshd-wrapper.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# Installed as /usr/sbin/sshd (real binary at /usr/sbin/sshd.real). -# atmoz entrypoint execs /usr/sbin/sshd — refresh keys + ownership first. -set -Eeo pipefail - -SFTP_UID="${SFTP_UID:-1001}" -SFTP_GID="${SFTP_GID:-100}" -DATA_DIR="${SFTP_DATA_DIR:-/home/sftp/syndicator}" - -for home in /home/*; do - [[ -d "$home" ]] || continue - user="$(basename "$home")" - keys_dir="$home/.ssh/keys" - [[ -d "$keys_dir" ]] || continue - auth="$home/.ssh/authorized_keys" - mkdir -p "$home/.ssh" - if compgen -G "$keys_dir/*" >/dev/null; then - cat "$keys_dir"/* | sort -u >"$auth" - else - : >"$auth" - fi - uid="$(id -u "$user" 2>/dev/null || echo "$SFTP_UID")" - chown "$uid" "$auth" - chmod 600 "$auth" -done - -if [[ -d "$DATA_DIR" ]]; then - chown -R "${SFTP_UID}:${SFTP_GID}" "$DATA_DIR" - chmod 755 "$DATA_DIR" -fi - -exec /usr/sbin/sshd.real "$@" diff --git a/systemd/syndicator-update.service b/systemd/syndicator-update.service deleted file mode 100644 index 85ed2d1..0000000 --- a/systemd/syndicator-update.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Rebuild syndicator compose images (n8n + pyautoflip) -After=docker.service -Requires=docker.service - -[Service] -Type=oneshot -WorkingDirectory=/home/benno/git/syndicator -ExecStart=/home/benno/git/syndicator/scripts/update.sh -User=benno -Group=benno diff --git a/systemd/syndicator-update.timer b/systemd/syndicator-update.timer deleted file mode 100644 index f64fa24..0000000 --- a/systemd/syndicator-update.timer +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Daily syndicator image update - -[Timer] -OnCalendar=*-*-* 04:00:00 -Persistent=true -Unit=syndicator-update.service - -[Install] -WantedBy=timers.target diff --git a/tests/integration/reframe.sh b/tests/integration/reframe.sh new file mode 100755 index 0000000..7c0752d --- /dev/null +++ b/tests/integration/reframe.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +image="${1:-syndicator-pyautoflip:local}" +tmp="$(mktemp -d)" +name="syndicator-reframe-${RANDOM}" +port="$(python3 - <<'PY' +import socket + +with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + print(sock.getsockname()[1]) +PY +)" +chmod 777 "$tmp" + +cleanup() { + status=$? + if [[ "$status" -ne 0 ]]; then + docker logs "$name" >&2 || true + fi + docker rm -f "$name" >/dev/null 2>&1 || true + rm -rf "$tmp" + exit "$status" +} +trap cleanup EXIT + +docker run -d --rm \ + --name "$name" \ + -p "127.0.0.1:${port}:8080" \ + -v "$tmp:/files" \ + "$image" >/dev/null + +for _ in $(seq 1 60); do + if curl -fsS "http://127.0.0.1:${port}/health" >/dev/null 2>&1; then + break + fi + sleep 1 +done +curl -fsS "http://127.0.0.1:${port}/health" >/dev/null + +docker exec "$name" ffmpeg \ + -hide_banner -loglevel error -y \ + -f lavfi -i "testsrc=size=320x180:rate=5" \ + -t 1 -pix_fmt yuv420p /files/input.mp4 + +curl -fsS --max-time 180 \ + -H 'Content-Type: application/json' \ + -d '{ + "input_path": "/files/input.mp4", + "output_path": "/files/output.mp4", + "aspect_ratio": "9:16", + "method": "saliency" + }' \ + "http://127.0.0.1:${port}/reframe" >"$tmp/response.json" + +python3 - "$tmp/response.json" "$tmp/output.mp4" <<'PY' +import json +from pathlib import Path +import sys + +response = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +output = Path(sys.argv[2]) +if not output.is_file() or output.stat().st_size == 0: + raise SystemExit("reframe did not create output media") +if response.get("width", 0) <= 0 or response.get("height", 0) <= 0: + raise SystemExit(f"invalid reframe dimensions: {response}") +PY + +echo "Production pyautoflip reframe smoke test passed." diff --git a/tests/integration/stack.sh b/tests/integration/stack.sh new file mode 100755 index 0000000..77cb8fc --- /dev/null +++ b/tests/integration/stack.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$ROOT" + +tmp="$(mktemp -d)" +project="syndicator-it-${RANDOM}" +env_file="$tmp/integration.env" + +free_port() { + python3 - <<'PY' +import socket + +with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + print(sock.getsockname()[1]) +PY +} + +n8n_port="$(free_port)" +sftp_port="$(free_port)" + +mkdir -p "$tmp/keys" +ssh-keygen -t ed25519 -f "$tmp/sftp_client" -N '' -C 'syndicator-it' "$env_file" <&2 || true + fi + compose down -v --remove-orphans >/dev/null 2>&1 || true + rm -rf "$tmp" + exit "$status" +} +trap cleanup EXIT + +"$ROOT/scripts/init.sh" +compose build +compose up -d --remove-orphans +"$ROOT/bin/syndicator" verify +if ! "$ROOT/bin/syndicator" verify | tee "$tmp/second-verify.log"; then + exit 1 +fi +if ! python3 - "$tmp/second-verify.log" <<'PY' +from pathlib import Path +import sys + +raise SystemExit(0 if "already current" in Path(sys.argv[1]).read_text() else 1) +PY +then + echo "Second verify did not skip an unchanged reconcile." >&2 + exit 1 +fi + +printf '%s\n' "integration payload" >"$tmp/upload.txt" +ssh-keyscan -p "$sftp_port" 127.0.0.1 >"$tmp/known_hosts" 2>/dev/null +cat >"$tmp/sftp.batch" </dev/null 2>&1 +status=$? +set -e + +if [[ "$status" -ne 2 ]]; then + echo "First init should request configuration and exit 2, got $status." >&2 + exit 1 +fi + +python3 - "$tmp/.env" <<'PY' +from pathlib import Path +import sys + +path = Path(sys.argv[1]) +values = {} +for line in path.read_text(encoding="utf-8").splitlines(): + if line and not line.startswith("#") and "=" in line: + key, value = line.split("=", 1) + values[key] = value +key = values.get("N8N_ENCRYPTION_KEY", "") +if len(key) != 64: + raise SystemExit("init did not generate a 256-bit n8n encryption key") +PY + +echo "First-run initialization test passed." diff --git a/tests/test_repository.py b/tests/test_repository.py new file mode 100644 index 0000000..9a27b4e --- /dev/null +++ b/tests/test_repository.py @@ -0,0 +1,238 @@ +from __future__ import annotations + +import json +import re +import subprocess +import unittest +from pathlib import Path +from typing import Any +from urllib.parse import unquote, urlparse + + +ROOT = Path(__file__).resolve().parents[1] +WORKFLOW_DIR = ROOT / "n8n" / "workflows" +CREDENTIAL_DIR = ROOT / "n8n" / "credentials" + + +def load_json(path: Path) -> Any: + return json.loads(path.read_text(encoding="utf-8")) + + +def workflow_reference(node: dict[str, Any]) -> str | None: + value = node.get("parameters", {}).get("workflowId") + if isinstance(value, str): + return value + if isinstance(value, dict): + nested = value.get("value") + if isinstance(nested, str): + return nested + return None + + +class RepositoryManifestTests(unittest.TestCase): + def setUp(self) -> None: + self.workflow_paths = sorted(WORKFLOW_DIR.glob("*.json")) + self.workflows = {path: load_json(path) for path in self.workflow_paths} + + self.credential_paths = sorted(CREDENTIAL_DIR.glob("*.template.json")) + self.credentials: list[dict[str, Any]] = [] + for path in self.credential_paths: + payload = load_json(path) + self.assertIsInstance(payload, list, path) + self.credentials.extend(payload) + + def test_expected_workflows_are_versioned(self) -> None: + self.assertEqual( + {path.stem for path in self.workflow_paths}, + { + "Adapt Feature Image", + "Adapt Hugo Media", + "Adapt Reel Media", + "Blog Post Publish", + "Reel Publish", + }, + ) + + def test_workflows_use_local_files_instead_of_ftp(self) -> None: + for path, workflow in self.workflows.items(): + for node in workflow.get("nodes", []): + self.assertNotEqual( + node.get("type"), + "n8n-nodes-base.ftp", + f"{path.name}: {node.get('name')}", + ) + self.assertNotIn( + "sftp", + node.get("credentials") or {}, + f"{path.name}: {node.get('name')}", + ) + + def test_workflow_names_and_ids_are_unique(self) -> None: + ids: list[str] = [] + for path, workflow in self.workflows.items(): + self.assertIsInstance(workflow, dict, path) + self.assertEqual(workflow.get("name"), path.stem, path) + workflow_id = workflow.get("id") + self.assertIsInstance(workflow_id, str, path) + self.assertTrue(workflow_id, path) + ids.append(workflow_id) + + self.assertEqual(len(ids), len(set(ids)), "workflow IDs must be unique") + + def test_webhook_nodes_have_stable_production_paths(self) -> None: + found: dict[str, str] = {} + for path, workflow in self.workflows.items(): + for node in workflow.get("nodes") or []: + if node.get("type") != "n8n-nodes-base.webhook": + continue + parameters = node.get("parameters") or {} + hook = str(parameters.get("path") or "").strip().strip("/") + self.assertTrue(node.get("webhookId"), f"{path.name}: missing webhookId") + self.assertEqual(parameters.get("httpMethod"), "POST", path.name) + self.assertTrue(hook, path.name) + found[hook] = path.name + self.assertEqual(set(found), {"publish", "reel"}) + + def test_subworkflow_references_resolve(self) -> None: + known_ids = {workflow["id"] for workflow in self.workflows.values()} + for path, workflow in self.workflows.items(): + for node in workflow.get("nodes", []): + referenced_id = workflow_reference(node) + if referenced_id is not None: + self.assertIn( + referenced_id, + known_ids, + f"{path.name}: {node.get('name')} references an unknown workflow", + ) + + def test_workflow_order_places_dependencies_first(self) -> None: + result = subprocess.run( + ["python3", str(ROOT / "scripts" / "workflow_order.py"), str(ROOT)], + check=True, + capture_output=True, + text=True, + ) + ordered_paths = [ROOT / line for line in result.stdout.splitlines()] + positions = { + load_json(path)["id"]: position + for position, path in enumerate(ordered_paths) + } + self.assertEqual(set(ordered_paths), set(self.workflow_paths)) + for path, workflow in self.workflows.items(): + for node in workflow.get("nodes", []): + dependency = workflow_reference(node) + if dependency is not None: + self.assertLess( + positions[dependency], + positions[workflow["id"]], + f"{path.name}: dependency must be imported first", + ) + + def test_workflow_exports_exclude_instance_state(self) -> None: + for path, workflow in self.workflows.items(): + self.assertFalse(workflow.get("pinData"), path) + self.assertNotIn("shared", workflow, path) + self.assertNotIn("versionMetadata", workflow, path) + self.assertNotIn("instanceId", workflow.get("meta", {}), path) + + def test_credential_ids_and_names_are_unique(self) -> None: + ids = [credential.get("id") for credential in self.credentials] + names = [credential.get("name") for credential in self.credentials] + self.assertTrue(all(isinstance(value, str) and value for value in ids)) + self.assertTrue(all(isinstance(value, str) and value for value in names)) + self.assertEqual(len(ids), len(set(ids)), "credential IDs must be unique") + self.assertEqual(len(names), len(set(names)), "credential names must be unique") + + def test_workflow_credential_references_resolve(self) -> None: + known_ids = {credential["id"] for credential in self.credentials} + for path, workflow in self.workflows.items(): + for node in workflow.get("nodes", []): + for reference in node.get("credentials", {}).values(): + credential_id = reference.get("id") + self.assertIn( + credential_id, + known_ids, + f"{path.name}: {node.get('name')} references an unknown credential", + ) + + def test_credential_templates_contain_only_placeholders(self) -> None: + placeholder = re.compile(r"^\$\{[A-Z][A-Z0-9_]*\}$") + for credential in self.credentials: + data = credential.get("data") + self.assertIsInstance(data, dict, credential.get("name")) + for key, value in data.items(): + self.assertIsInstance(value, str, f"{credential.get('name')}.{key}") + self.assertRegex(value, placeholder, f"{credential.get('name')}.{key}") + + def test_runtime_dependencies_are_locked(self) -> None: + runtime_files = [ + ROOT / "docker-compose.yml", + ROOT / "n8n" / "Dockerfile", + ROOT / "pyautoflip" / "Dockerfile", + ] + runtime_config = "\n".join( + path.read_text(encoding="utf-8") for path in runtime_files + ) + self.assertNotIn(":stable", runtime_config) + self.assertGreaterEqual( + len(re.findall(r"@sha256:[0-9a-f]{64}", runtime_config)), + 5, + "container bases should be immutable by default", + ) + self.assertNotRegex(runtime_config, r"ARG [A-Z0-9_]+_IMAGE=") + self.assertNotIn("pyautoflip_home", runtime_config) + + package = load_json(ROOT / "n8n" / "package.json") + for version in package["dependencies"].values(): + self.assertRegex(version, r"^\d+\.\d+\.\d+$") + self.assertTrue((ROOT / "n8n" / "package-lock.json").is_file()) + + requirements = (ROOT / "pyautoflip" / "requirements.txt").read_text( + encoding="utf-8" + ) + self.assertIn("--hash=sha256:", requirements) + self.assertIn("torch==2.13.0+cpu", requirements) + self.assertNotIn("\nnvidia-", requirements) + self.assertTrue((ROOT / "pyautoflip" / "requirements.in").is_file()) + + dependabot = (ROOT / ".github" / "dependabot.yml").read_text( + encoding="utf-8" + ) + self.assertIn("package-ecosystem: docker-compose", dependabot) + + def test_example_configuration_is_host_neutral(self) -> None: + example = (ROOT / ".env.example").read_text(encoding="utf-8") + self.assertNotRegex(example, r"\b192\.168\.\d{1,3}\.\d{1,3}\b") + + def test_reconcile_uses_supported_interfaces(self) -> None: + reconcile = (ROOT / "n8n" / "reconcile.js").read_text(encoding="utf-8") + self.assertNotIn("docker volume", reconcile) + self.assertNotIn("sqlite", reconcile.lower()) + self.assertNotIn("PUBLISH_WORKFLOW_IDS", reconcile) + self.assertNotIn("publish:workflow", reconcile) + self.assertIn("/rest/workflows/", reconcile) + self.assertIn("/deactivate", reconcile) + self.assertIn("/activate", reconcile) + self.assertIn("active = false", reconcile) + verify = (ROOT / "scripts" / "verify.sh").read_text(encoding="utf-8") + self.assertIn("Did you mean to make a POST request", verify) + self.assertNotIn("*[Ww]ebhook*", verify) + library = (ROOT / "scripts" / "lib.sh").read_text(encoding="utf-8") + self.assertIn("/healthz/readiness", library) + link_pattern = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)") + for document in ROOT.rglob("*.md"): + if {".git", ".venv", "node_modules"}.intersection(document.parts): + continue + for raw_target in link_pattern.findall( + document.read_text(encoding="utf-8") + ): + target = raw_target.split(maxsplit=1)[0].strip("<>") + parsed = urlparse(target) + if parsed.scheme or target.startswith("#"): + continue + path = (document.parent / unquote(parsed.path)).resolve() + self.assertTrue(path.exists(), f"{document}: broken link {target}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/validate-compose.sh b/tests/validate-compose.sh new file mode 100755 index 0000000..2ff5243 --- /dev/null +++ b/tests/validate-compose.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +export N8N_ENCRYPTION_KEY="ci-only-encryption-key" +export N8N_OWNER_EMAIL="ci@example.invalid" +export N8N_OWNER_PASSWORD="ci-owner-password" +export OPENAI_API_KEY="ci-openai-key" +export POSTIZ_API_KEY="ci-postiz-key" + +if [[ "$#" -gt 0 ]]; then + docker compose --env-file /dev/null "$@" +else + docker compose --env-file /dev/null config --quiet +fi