diff --git a/.env.example b/.env.example
index 31efd9d..76671b4 100644
--- a/.env.example
+++ b/.env.example
@@ -1,58 +1,58 @@
-# AIVIDIO / VIDMATION Configuration
+# AIVIDIO Configuration
# Copy to .env and fill in your values
# Production: https://aividio.com
# --- LLM Providers ---
-VIDMATION_ANTHROPIC_API_KEY=sk-ant-...
-VIDMATION_OPENAI_API_KEY=sk-...
+AIVIDIO_ANTHROPIC_API_KEY=sk-ant-...
+AIVIDIO_OPENAI_API_KEY=sk-...
# --- TTS Providers ---
-VIDMATION_ELEVENLABS_API_KEY=...
+AIVIDIO_ELEVENLABS_API_KEY=...
# --- AI Model Platforms ---
-VIDMATION_REPLICATE_API_TOKEN=r8_...
-VIDMATION_FAL_KEY=...
+AIVIDIO_REPLICATE_API_TOKEN=r8_...
+AIVIDIO_FAL_KEY=...
# --- Stock Media ---
-VIDMATION_PEXELS_API_KEY=...
-VIDMATION_PIXABAY_API_KEY=...
+AIVIDIO_PEXELS_API_KEY=...
+AIVIDIO_PIXABAY_API_KEY=...
# --- Image Generation ---
# Uses OpenAI key for DALL-E, or Replicate/fal for Flux/SD
# --- Database ---
# Development (SQLite):
-# VIDMATION_DATABASE_URL=sqlite:///data/vidmation.db
+# AIVIDIO_DATABASE_URL=sqlite:///data/aividio.db
# Production (PostgreSQL):
-VIDMATION_DATABASE_URL=postgresql://aividio_user:PASSWORD@localhost:5432/aividio_prod
+AIVIDIO_DATABASE_URL=postgresql://aividio_user:PASSWORD@localhost:5432/aividio_prod
# --- Web ---
-VIDMATION_SECRET_KEY=change-me-use-openssl-rand-hex-32
-VIDMATION_WEB_HOST=0.0.0.0
-VIDMATION_WEB_PORT=8001
+AIVIDIO_SECRET_KEY=change-me-use-openssl-rand-hex-32
+AIVIDIO_WEB_HOST=0.0.0.0
+AIVIDIO_WEB_PORT=8001
# --- Auth / JWT ---
-VIDMATION_JWT_SECRET=change-me-use-openssl-rand-hex-32
-VIDMATION_JWT_ACCESS_TOKEN_EXPIRE_MINUTES=15
-VIDMATION_JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
+AIVIDIO_JWT_SECRET=change-me-use-openssl-rand-hex-32
+AIVIDIO_JWT_ACCESS_TOKEN_EXPIRE_MINUTES=15
+AIVIDIO_JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
# --- Queue (optional, default uses SQLite) ---
-VIDMATION_USE_REDIS=false
-VIDMATION_REDIS_URL=redis://localhost:6379/0
+AIVIDIO_USE_REDIS=false
+AIVIDIO_REDIS_URL=redis://localhost:6379/0
# --- Defaults ---
-VIDMATION_DEFAULT_LLM_PROVIDER=claude
-VIDMATION_DEFAULT_TTS_PROVIDER=elevenlabs
-VIDMATION_DEFAULT_IMAGE_PROVIDER=dalle
-VIDMATION_DEFAULT_VIDEO_FORMAT=landscape
+AIVIDIO_DEFAULT_LLM_PROVIDER=claude
+AIVIDIO_DEFAULT_TTS_PROVIDER=elevenlabs
+AIVIDIO_DEFAULT_IMAGE_PROVIDER=dalle
+AIVIDIO_DEFAULT_VIDEO_FORMAT=landscape
# --- Stripe Billing ---
-# VIDMATION_STRIPE_SECRET_KEY=sk_live_...
-# VIDMATION_STRIPE_PUBLISHABLE_KEY=pk_live_...
-# VIDMATION_STRIPE_WEBHOOK_SECRET=whsec_...
+# AIVIDIO_STRIPE_SECRET_KEY=sk_live_...
+# AIVIDIO_STRIPE_PUBLISHABLE_KEY=pk_live_...
+# AIVIDIO_STRIPE_WEBHOOK_SECRET=whsec_...
# --- Notifications ---
-# VIDMATION_RESEND_API_KEY=re_...
-# VIDMATION_EMAIL_FROM=noreply@aividio.com
-# VIDMATION_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
-# VIDMATION_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
+# AIVIDIO_RESEND_API_KEY=re_...
+# AIVIDIO_EMAIL_FROM=noreply@aividio.com
+# AIVIDIO_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
+# AIVIDIO_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index b86b854..090da18 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -1,4 +1,4 @@
-name: Deploy VIDMATION
+name: Deploy AIVIDIO
on:
push:
diff --git a/Dockerfile b/Dockerfile
index c049f20..29b1b04 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -21,4 +21,4 @@ RUN mkdir -p data output assets/fonts assets/music channel_profiles
EXPOSE 8000
# Default: run web server
-CMD ["uvicorn", "vidmation.web.app:create_app", "--factory", "--host", "0.0.0.0", "--port", "8000"]
+CMD ["uvicorn", "aividio.web.app:create_app", "--factory", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/alembic.ini b/alembic.ini
index 3c42c72..516d7f0 100644
--- a/alembic.ini
+++ b/alembic.ini
@@ -1,6 +1,6 @@
[alembic]
script_location = migrations
-sqlalchemy.url = sqlite:///data/vidmation.db
+sqlalchemy.url = sqlite:///data/aividio.db
[loggers]
keys = root,sqlalchemy,alembic
diff --git a/deploy/aividio-api.service b/deploy/aividio-api.service
index 04174fb..3934a3f 100644
--- a/deploy/aividio-api.service
+++ b/deploy/aividio-api.service
@@ -9,7 +9,7 @@ User=root
Group=root
WorkingDirectory=/var/www/aividio
EnvironmentFile=/var/www/aividio/.env
-ExecStart=/var/www/aividio/.venv/bin/uvicorn vidmation.web.app:create_app --factory --host 127.0.0.1 --port 8001 --workers 2
+ExecStart=/var/www/aividio/.venv/bin/uvicorn aividio.web.app:create_app --factory --host 127.0.0.1 --port 8001 --workers 2
Restart=always
RestartSec=10
StandardOutput=journal
diff --git a/docker-compose.yml b/docker-compose.yml
index e0a3378..e80b356 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,7 +15,7 @@ services:
worker:
build: .
- command: ["python", "-m", "vidmation", "worker"]
+ command: ["python", "-m", "aividio", "worker"]
volumes:
- ./data:/app/data
- ./output:/app/output
diff --git a/frontend/next-env.d 2.ts b/frontend/next-env.d 2.ts
new file mode 100644
index 0000000..9edff1c
--- /dev/null
+++ b/frontend/next-env.d 2.ts
@@ -0,0 +1,6 @@
+///
+///
+import "./.next/types/routes.d.ts";
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/frontend/next.config.ts b/frontend/next.config.ts
new file mode 100644
index 0000000..1743977
--- /dev/null
+++ b/frontend/next.config.ts
@@ -0,0 +1,14 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ async rewrites() {
+ return [
+ {
+ source: "/api/:path*",
+ destination: "http://localhost:8001/api/:path*",
+ },
+ ];
+ },
+};
+
+export default nextConfig;
diff --git a/frontend/package 2.json b/frontend/package 2.json
new file mode 100644
index 0000000..7e84e32
--- /dev/null
+++ b/frontend/package 2.json
@@ -0,0 +1,45 @@
+{
+ "name": "frontend",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "type": "module",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-label": "^2.1.8",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@tailwindcss/postcss": "^4.2.2",
+ "@types/node": "^25.5.2",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^1.7.0",
+ "next": "^16.2.2",
+ "postcss": "^8.5.8",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "recharts": "^3.8.1",
+ "tailwind-merge": "^3.5.0",
+ "tailwindcss": "^4.2.2",
+ "typescript": "^6.0.2"
+ }
+}
diff --git a/frontend/package-lock 2.json b/frontend/package-lock 2.json
new file mode 100644
index 0000000..d31b9b3
--- /dev/null
+++ b/frontend/package-lock 2.json
@@ -0,0 +1,3351 @@
+{
+ "name": "frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-label": "^2.1.8",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@tailwindcss/postcss": "^4.2.2",
+ "@types/node": "^25.5.2",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^1.7.0",
+ "next": "^16.2.2",
+ "postcss": "^8.5.8",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "recharts": "^3.8.1",
+ "tailwind-merge": "^3.5.0",
+ "tailwindcss": "^4.2.2",
+ "typescript": "^6.0.2"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+ "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz",
+ "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.2.tgz",
+ "integrity": "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ==",
+ "license": "MIT"
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.2.tgz",
+ "integrity": "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.2.tgz",
+ "integrity": "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.2.tgz",
+ "integrity": "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.2.tgz",
+ "integrity": "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.2.tgz",
+ "integrity": "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.2.tgz",
+ "integrity": "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.2.tgz",
+ "integrity": "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.2.tgz",
+ "integrity": "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
+ "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
+ "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
+ "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz",
+ "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz",
+ "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
+ "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz",
+ "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
+ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
+ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
+ "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
+ "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
+ "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@standard-schema/utils": "^0.3.0",
+ "immer": "^11.0.0",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@reduxjs/toolkit/node_modules/immer": {
+ "version": "11.1.4",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
+ "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
+ "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.2.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
+ "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.2.2",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.2",
+ "@tailwindcss/oxide-darwin-x64": "4.2.2",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.2",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.2",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.2",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
+ "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
+ "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
+ "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
+ "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
+ "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
+ "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
+ "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
+ "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
+ "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
+ "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
+ "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
+ "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz",
+ "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.2.2",
+ "@tailwindcss/oxide": "4.2.2",
+ "postcss": "^8.5.6",
+ "tailwindcss": "4.2.2"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.5.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz",
+ "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.14",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.14.tgz",
+ "integrity": "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001785",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz",
+ "integrity": "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
+ "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-toolkit": {
+ "version": "1.45.1",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
+ "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
+ "license": "MIT",
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
+ "node_modules/eventemitter3": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
+ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
+ "license": "MIT"
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/immer": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
+ "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz",
+ "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "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/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "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/next": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/next/-/next-16.2.2.tgz",
+ "integrity": "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "16.2.2",
+ "@swc/helpers": "0.5.15",
+ "baseline-browser-mapping": "^2.9.19",
+ "caniuse-lite": "^1.0.30001579",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.6"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "16.2.2",
+ "@next/swc-darwin-x64": "16.2.2",
+ "@next/swc-linux-arm64-gnu": "16.2.2",
+ "@next/swc-linux-arm64-musl": "16.2.2",
+ "@next/swc-linux-x64-gnu": "16.2.2",
+ "@next/swc-linux-x64-musl": "16.2.2",
+ "@next/swc-win32-arm64-msvc": "16.2.2",
+ "@next/swc-win32-x64-msvc": "16.2.2",
+ "sharp": "^0.34.5"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.51.1",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
+ "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/react-redux": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+ "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/recharts": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz",
+ "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==",
+ "license": "MIT",
+ "workspaces": [
+ "www"
+ ],
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.9.0 || 2.x.x",
+ "clsx": "^2.1.1",
+ "decimal.js-light": "^2.5.1",
+ "es-toolkit": "^1.39.3",
+ "eventemitter3": "^5.0.1",
+ "immer": "^10.1.1",
+ "react-redux": "8.x.x || 9.x.x",
+ "reselect": "5.1.1",
+ "tiny-invariant": "^1.3.3",
+ "use-sync-external-store": "^1.2.2",
+ "victory-vendor": "^37.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
+ "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
+ "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
+ "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "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/typescript": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz",
+ "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "license": "MIT"
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/victory-vendor": {
+ "version": "37.3.6",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
+ "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ }
+ }
+}
diff --git a/frontend/postcss.config 2.mjs b/frontend/postcss.config 2.mjs
new file mode 100644
index 0000000..79bcf13
--- /dev/null
+++ b/frontend/postcss.config 2.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/frontend/public 2/aividio-logo-bold.png b/frontend/public 2/aividio-logo-bold.png
new file mode 100644
index 0000000..a32246d
Binary files /dev/null and b/frontend/public 2/aividio-logo-bold.png differ
diff --git a/frontend/public 2/aividio-logo.png b/frontend/public 2/aividio-logo.png
new file mode 100644
index 0000000..e683664
Binary files /dev/null and b/frontend/public 2/aividio-logo.png differ
diff --git a/frontend/public 2/aividio-monogram.png b/frontend/public 2/aividio-monogram.png
new file mode 100644
index 0000000..25d0fc9
Binary files /dev/null and b/frontend/public 2/aividio-monogram.png differ
diff --git a/frontend/public 2/aividio-official.png b/frontend/public 2/aividio-official.png
new file mode 100644
index 0000000..b10cbcf
Binary files /dev/null and b/frontend/public 2/aividio-official.png differ
diff --git a/frontend/public 2/favicon.svg b/frontend/public 2/favicon.svg
new file mode 100644
index 0000000..3a830b4
--- /dev/null
+++ b/frontend/public 2/favicon.svg
@@ -0,0 +1,4 @@
+
+
+ Ai
+
diff --git a/frontend/public 2/logo-wide.svg b/frontend/public 2/logo-wide.svg
new file mode 100644
index 0000000..a451cd0
--- /dev/null
+++ b/frontend/public 2/logo-wide.svg
@@ -0,0 +1,20 @@
+
+
+
+ Ai
+
+
+
+ AIVidio
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/public 2/logo.svg b/frontend/public 2/logo.svg
new file mode 100644
index 0000000..c10406e
--- /dev/null
+++ b/frontend/public 2/logo.svg
@@ -0,0 +1,24 @@
+
+
+
+
+
+ Ai
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/(dashboard)/page.tsx b/frontend/src/app/(dashboard)/page.tsx
new file mode 100644
index 0000000..b0d4373
--- /dev/null
+++ b/frontend/src/app/(dashboard)/page.tsx
@@ -0,0 +1,135 @@
+import { Suspense } from "react";
+import Link from "next/link";
+import { ActiveJobs } from "@/components/dashboard/active-jobs";
+import { CostWidget } from "@/components/dashboard/cost-widget";
+import { DashboardVideos } from "@/components/dashboard/recent-videos";
+
+const API_BASE =
+ process.env.NEXT_PUBLIC_API_URL || "http://localhost:8001/api/v1";
+
+async function getStats() {
+ try {
+ const [videosRes, channelsRes, jobsRes] = await Promise.all([
+ fetch(`${API_BASE}/videos`, { cache: "no-store" }),
+ fetch(`${API_BASE}/channels`, { cache: "no-store" }),
+ fetch(`${API_BASE}/jobs?status=running`, { cache: "no-store" }),
+ ]);
+
+ const videos = videosRes.ok ? await videosRes.json() : [];
+ const channels = channelsRes.ok ? await channelsRes.json() : [];
+ const activeJobs = jobsRes.ok ? await jobsRes.json() : [];
+
+ const uploaded = Array.isArray(videos)
+ ? videos.filter((v: { status: string }) => v.status === "uploaded").length
+ : 0;
+
+ return {
+ totalVideos: Array.isArray(videos) ? videos.length : 0,
+ uploaded,
+ activeJobs: Array.isArray(activeJobs) ? activeJobs.length : 0,
+ channels: Array.isArray(channels) ? channels.length : 0,
+ };
+ } catch {
+ return { totalVideos: 0, uploaded: 0, activeJobs: 0, channels: 0 };
+ }
+}
+
+export default async function DashboardPage() {
+ const stats = await getStats();
+
+ return (
+
+
Dashboard
+
+
+ {/* Main content */}
+
+ {/* Stats row */}
+
+
+
+
+
+
+
+ {/* Quick actions */}
+
+
+
+
+
+
+ {/* Active jobs (polling client component) */}
+
+
+
+
+ {/* Recent videos */}
+
+
+ Recent Videos
+
+
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+ }
+ >
+
+
+
+
+
+
+ {/* Right sidebar */}
+
+
+
+
+
+
+
+ );
+}
+
+function StatCard({ label, value }: { label: string; value: number }) {
+ return (
+
+
+ {value}
+
+
{label}
+
+ );
+}
+
+function QuickLink({ href, label }: { href: string; label: string }) {
+ return (
+
+ {label}
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/analytics/page.tsx b/frontend/src/app/analytics/page.tsx
new file mode 100644
index 0000000..a363880
--- /dev/null
+++ b/frontend/src/app/analytics/page.tsx
@@ -0,0 +1,232 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { DollarSign, TrendingUp, Film, Cpu, BarChart3 } from "lucide-react";
+import type { CostSummary } from "@/types";
+import { api } from "@/lib/api";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { PageHeader } from "@/components/shared/page-header";
+import { CostChart } from "@/components/analytics/cost-chart";
+
+interface AnalyticsStatCardProps {
+ label: string;
+ value: string;
+ icon: React.ReactNode;
+ sublabel?: string;
+}
+
+function AnalyticsStatCard({ label, value, icon, sublabel }: AnalyticsStatCardProps) {
+ return (
+
+
+
+
+ {label}
+
+
+ {value}
+
+ {sublabel && (
+
{sublabel}
+ )}
+
+
+ {icon}
+
+
+
+ );
+}
+
+export default function AnalyticsPage() {
+ const [costData, setCostData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ api
+ .getCosts()
+ .then((data) => setCostData(data as CostSummary))
+ .catch((err) => setError(err.message))
+ .finally(() => setLoading(false));
+ }, []);
+
+ const totalCost = costData?.total_cost ?? 0;
+ const totalCalls = costData?.total_calls ?? 0;
+ const avgCost = totalCalls > 0 ? totalCost / totalCalls : 0;
+ const serviceCount = costData ? Object.keys(costData.by_service).length : 0;
+
+ const todayCost =
+ costData?.daily_trend?.length
+ ? costData.daily_trend[costData.daily_trend.length - 1]?.cost ?? 0
+ : 0;
+
+ const sortedServices = costData
+ ? Object.entries(costData.by_service).sort(([, a], [, b]) => b - a)
+ : [];
+
+ return (
+
+
+
+
+ {/* Stat cards */}
+
+ {loading ? (
+ Array.from({ length: 4 }).map((_, i) => (
+
+ ))
+ ) : (
+ <>
+
}
+ sublabel={`${totalCalls} total calls`}
+ />
+
}
+ />
+
}
+ />
+
}
+ />
+ >
+ )}
+
+
+ {error && (
+
+ )}
+
+ {/* Daily cost chart */}
+
+
+ Daily Cost
+
+
+ {loading ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* Service breakdown + top videos */}
+
+ {/* Service breakdown */}
+
+
+
+
+ Service Breakdown
+
+
+
+ {loading ? (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ ) : sortedServices.length === 0 ? (
+
+ No usage data yet
+
+ ) : (
+
+ {sortedServices.map(([service, cost]) => {
+ const pct = totalCost > 0 ? (cost / totalCost) * 100 : 0;
+ return (
+
+
+ {service}
+
+ ${cost.toFixed(2)}
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+
+ {/* Top videos by cost */}
+
+
+
+
+ Top Videos by Cost
+
+
+
+ {loading ? (
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+ ) : sortedServices.length === 0 ? (
+
+ No video cost data yet
+
+ ) : (
+
+ {sortedServices.slice(0, 5).map(([service, cost], i) => (
+
+
+
+ {i + 1}
+
+
+ {service}
+
+
+
+ ${cost.toFixed(2)}
+
+
+ ))}
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/channels/page.tsx b/frontend/src/app/channels/page.tsx
new file mode 100644
index 0000000..3ffc0fb
--- /dev/null
+++ b/frontend/src/app/channels/page.tsx
@@ -0,0 +1,87 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import Link from "next/link";
+import { Plus, Tv2 } from "lucide-react";
+import type { Channel } from "@/types";
+import { api } from "@/lib/api";
+import { Button } from "@/components/ui/button";
+import { PageHeader } from "@/components/shared/page-header";
+import { EmptyState } from "@/components/shared/empty-state";
+import { ChannelCard } from "@/components/channels/channel-card";
+
+export default function ChannelsPage() {
+ const [channels, setChannels] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ api
+ .getChannels()
+ .then((data) => setChannels(data as Channel[]))
+ .catch((err) => setError(err.message))
+ .finally(() => setLoading(false));
+ }, []);
+
+ return (
+
+
+
+
+
+ Add Channel
+
+
+ }
+ />
+
+
+ {loading && (
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+ )}
+
+ {error && (
+
+ )}
+
+ {!loading && !error && channels.length === 0 && (
+
+
+
+ Add Channel
+
+
+ }
+ />
+ )}
+
+ {!loading && !error && channels.length > 0 && (
+
+ {channels.map((channel) => (
+
+ ))}
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/app/content/page.tsx b/frontend/src/app/content/page.tsx
new file mode 100644
index 0000000..fd304fd
--- /dev/null
+++ b/frontend/src/app/content/page.tsx
@@ -0,0 +1,63 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { Button } from "@/components/ui/button";
+import { CalendarGrid } from "@/components/content/calendar-grid";
+import { TrendingTopics } from "@/components/content/trending-topics";
+import { Sparkles, Calendar } from "lucide-react";
+
+export default function ContentPage() {
+ const [isGenerating, setIsGenerating] = useState(false);
+ const router = useRouter();
+
+ const handleGenerateCalendar = () => {
+ setIsGenerating(true);
+ // Simulate calendar generation
+ setTimeout(() => setIsGenerating(false), 2000);
+ };
+
+ return (
+
+
+ {/* Page header */}
+
+
+
+
Content
+
+
+
+ {isGenerating ? "Generating..." : "Generate Calendar"}
+
+
+
+ {/* Main layout: calendar + trending sidebar */}
+
+ {/* Calendar grid - left 2/3 */}
+
+ {
+ router.push(`/videos/new?topic=${encodeURIComponent(item.topic)}`);
+ }}
+ onAddClick={(date) => {
+ // TODO: Open content creation for this date
+ }}
+ />
+
+
+ {/* Trending topics sidebar - right 1/3 */}
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/forgot-password/page.tsx b/frontend/src/app/forgot-password/page.tsx
new file mode 100644
index 0000000..4d03783
--- /dev/null
+++ b/frontend/src/app/forgot-password/page.tsx
@@ -0,0 +1,154 @@
+"use client";
+
+import { useState, type FormEvent } from "react";
+import Link from "next/link";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { ArrowLeft, Loader2, CheckCircle2 } from "lucide-react";
+
+const API_BASE = process.env.NEXT_PUBLIC_API_URL || "/api/v1";
+
+export default function ForgotPasswordPage() {
+ const [email, setEmail] = useState("");
+ const [error, setError] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [submitted, setSubmitted] = useState(false);
+
+ async function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+ setError("");
+ setIsSubmitting(true);
+
+ try {
+ const res = await fetch(`${API_BASE}/auth/forgot-password`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email }),
+ });
+
+ if (!res.ok) {
+ const err = await res
+ .json()
+ .catch(() => ({ detail: "Request failed" }));
+ throw new Error(err.detail || "Could not send reset link");
+ }
+
+ setSubmitted(true);
+ } catch (err) {
+ // Always show success to prevent email enumeration
+ setSubmitted(true);
+ } finally {
+ setIsSubmitting(false);
+ }
+ }
+
+ return (
+
+
+ {/* Logo */}
+
+
+
+ Ai
+
+
+
+ Reset your password
+
+
+
+ {/* Card */}
+
+ {submitted ? (
+ /* Success state */
+
+
+
+
+
+
+ Check your email
+
+
+ If an account exists for{" "}
+ {email} , we sent a link
+ to reset your password.
+
+
+
+
+ Back to sign in
+
+
+ ) : (
+ /* Form state */
+ <>
+
+ Enter the email address associated with your account and
+ we'll send you a link to reset your password.
+
+
+ >
+ )}
+
+
+ {/* Footer link */}
+ {!submitted && (
+
+
+
+ Back to sign in
+
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
new file mode 100644
index 0000000..461e0cc
--- /dev/null
+++ b/frontend/src/app/globals.css
@@ -0,0 +1,188 @@
+@import "tailwindcss";
+
+@theme {
+ /* OpenAI-inspired color system */
+ --color-background: #0d0d0d;
+ --color-surface: #1a1a1a;
+ --color-surface-2: #2a2a2a;
+ --color-border: rgba(255, 255, 255, 0.08);
+ --color-border-hover: rgba(255, 255, 255, 0.15);
+
+ --color-text-primary: #ececec;
+ --color-text-secondary: #999999;
+ --color-text-muted: #666666;
+
+ --color-accent: #10a37f;
+ --color-accent-hover: #1a7f64;
+ --color-accent-subtle: rgba(16, 163, 127, 0.12);
+
+ --color-success: #10a37f;
+ --color-warning: #f59e0b;
+ --color-error: #ef4444;
+ --color-info: #6366f1;
+
+ /* Spacing */
+ --spacing-xs: 4px;
+ --spacing-sm: 8px;
+ --spacing-md: 16px;
+ --spacing-lg: 24px;
+ --spacing-xl: 32px;
+ --spacing-2xl: 48px;
+
+ /* Radii */
+ --radius-sm: 8px;
+ --radius-md: 12px;
+ --radius-lg: 16px;
+ --radius-xl: 20px;
+
+ /* Typography */
+ --font-sans: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ --font-mono: "JetBrains Mono", "Fira Code", monospace;
+}
+
+/* Base styles */
+body {
+ background-color: var(--color-background);
+ color: var(--color-text-primary);
+ font-family: var(--font-sans);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+/* Custom scrollbar */
+::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 3px;
+}
+::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.2);
+}
+
+/* Focus styles */
+*:focus-visible {
+ outline: 2px solid var(--color-accent);
+ outline-offset: 2px;
+}
+
+/* Pulse animation for active status */
+@keyframes pulse-dot {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.4; }
+}
+.animate-pulse-dot {
+ animation: pulse-dot 2s ease-in-out infinite;
+}
+
+/* Progress bar transition */
+.progress-bar {
+ transition: width 500ms ease-out;
+}
+
+/* ============================================================
+ Landing Page Utilities
+ ============================================================ */
+
+/* Gradient text — green to teal */
+.gradient-text {
+ background: linear-gradient(135deg, #10a37f, #34d399, #6ee7b7);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+/* Green glow for buttons and cards */
+.glow-green {
+ box-shadow: 0 0 60px rgba(16, 163, 127, 0.15);
+}
+.glow-green-sm {
+ box-shadow: 0 0 30px rgba(16, 163, 127, 0.12);
+}
+.glow-green-button {
+ box-shadow: 0 0 20px rgba(16, 163, 127, 0.25), 0 0 60px rgba(16, 163, 127, 0.1);
+}
+
+/* Dot grid background pattern */
+.dot-pattern {
+ background-image: radial-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px);
+ background-size: 24px 24px;
+}
+
+/* Floating animation for hero visual */
+@keyframes float {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-12px); }
+}
+.animate-float {
+ animation: float 6s ease-in-out infinite;
+}
+
+/* Subtle shimmer for loading/decorative */
+@keyframes shimmer {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
+}
+.animate-shimmer {
+ background-size: 200% 100%;
+ animation: shimmer 8s linear infinite;
+}
+
+/* Gradient border card effect */
+.gradient-border {
+ position: relative;
+ background: #111111;
+ border-radius: 16px;
+}
+.gradient-border::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: 16px;
+ padding: 1px;
+ background: linear-gradient(135deg, rgba(16, 163, 127, 0.3), rgba(16, 163, 127, 0.05), rgba(52, 211, 153, 0.15));
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+ pointer-events: none;
+}
+
+/* Hide scrollbar for style showcase horizontal scroll */
+.hide-scrollbar {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+.hide-scrollbar::-webkit-scrollbar {
+ display: none;
+}
+
+/* Radial glow behind hero */
+.hero-glow {
+ background: radial-gradient(ellipse 600px 400px at 50% 40%, rgba(16, 163, 127, 0.06), transparent);
+}
+
+/* Smooth scroll for the whole page */
+html {
+ scroll-behavior: smooth;
+}
+
+/* Bento card hover glow */
+.bento-card {
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
+}
+.bento-card:hover {
+ border-color: rgba(16, 163, 127, 0.15);
+ box-shadow: 0 0 40px rgba(16, 163, 127, 0.06);
+ transform: translateY(-2px);
+}
+
+/* Pricing card popular glow */
+.pricing-popular {
+ border-color: rgba(16, 163, 127, 0.4);
+ box-shadow: 0 0 80px rgba(16, 163, 127, 0.08), 0 0 30px rgba(16, 163, 127, 0.05);
+}
diff --git a/frontend/src/app/jobs/page.tsx b/frontend/src/app/jobs/page.tsx
new file mode 100644
index 0000000..d0a0fd5
--- /dev/null
+++ b/frontend/src/app/jobs/page.tsx
@@ -0,0 +1,138 @@
+"use client";
+
+import { useEffect, useState, useCallback, useRef } from "react";
+import { Layers } from "lucide-react";
+import type { Job, JobStatus } from "@/types";
+import { api } from "@/lib/api";
+import { PageHeader } from "@/components/shared/page-header";
+import { EmptyState } from "@/components/shared/empty-state";
+import { JobCard } from "@/components/jobs/job-card";
+
+const STATUS_TABS: { label: string; value: JobStatus | "all" }[] = [
+ { label: "All", value: "all" },
+ { label: "Running", value: "running" },
+ { label: "Queued", value: "queued" },
+ { label: "Completed", value: "completed" },
+ { label: "Failed", value: "failed" },
+ { label: "Cancelled", value: "cancelled" },
+];
+
+export default function JobsPage() {
+ const [jobs, setJobs] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [filter, setFilter] = useState("all");
+ const intervalRef = useRef | null>(null);
+
+ const fetchJobs = useCallback(async () => {
+ try {
+ const params: Record = {};
+ if (filter !== "all") params.status = filter;
+ const data = await api.getJobs(params);
+ setJobs(data as Job[]);
+ setError(null);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to load jobs");
+ } finally {
+ setLoading(false);
+ }
+ }, [filter]);
+
+ useEffect(() => {
+ setLoading(true);
+ fetchJobs();
+ }, [fetchJobs]);
+
+ // Poll for running job updates
+ useEffect(() => {
+ const hasActive = jobs.some(
+ (j) => j.status === "running" || j.status === "queued"
+ );
+ if (hasActive) {
+ intervalRef.current = setInterval(fetchJobs, 3000);
+ }
+ return () => {
+ if (intervalRef.current) clearInterval(intervalRef.current);
+ };
+ }, [jobs, fetchJobs]);
+
+ async function handleRetry(id: string) {
+ try {
+ await api.retryJob(id);
+ fetchJobs();
+ } catch {
+ // Silently handle -- card reflects current state on next poll
+ }
+ }
+
+ // API already returns filtered results when filter !== "all"
+ const filteredJobs = jobs;
+
+ return (
+
+
+
+
+ {/* Filter tabs */}
+
+ {STATUS_TABS.map((tab) => (
+ setFilter(tab.value)}
+ className={`rounded-lg px-3.5 py-1.5 text-xs font-medium transition-all duration-150 ${
+ filter === tab.value
+ ? "bg-white/[0.08] text-[#ececec]"
+ : "text-[#666] hover:text-[#999]"
+ }`}
+ >
+ {tab.label}
+
+ ))}
+
+
+ {/* Content */}
+
+ {loading && (
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+ )}
+
+ {error && (
+
+ )}
+
+ {!loading && !error && filteredJobs.length === 0 && (
+
+ )}
+
+ {!loading && !error && filteredJobs.length > 0 && (
+
+ {filteredJobs.map((job) => (
+
+ ))}
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/app/landing/layout.tsx b/frontend/src/app/landing/layout.tsx
new file mode 100644
index 0000000..45b1ca5
--- /dev/null
+++ b/frontend/src/app/landing/layout.tsx
@@ -0,0 +1,33 @@
+import type { Metadata } from "next";
+import { LandingShell } from "./components/landing-shell";
+
+export const metadata: Metadata = {
+ title: "AIVIDIO — Create Faceless YouTube Videos with AI",
+ description:
+ "From topic to published video in minutes. AI writes, narrates, and produces professional videos.",
+ icons: {
+ icon: "/favicon.svg",
+ },
+ openGraph: {
+ title: "AIVIDIO — Create Faceless YouTube Videos with AI",
+ description:
+ "From topic to published video in minutes. AI writes, narrates, and produces professional videos.",
+ images: [{ url: "/aividio-logo.png" }],
+ type: "website",
+ },
+ twitter: {
+ card: "summary",
+ title: "AIVIDIO — Create Faceless YouTube Videos with AI",
+ description:
+ "From topic to published video in minutes. AI writes, narrates, and produces professional videos.",
+ images: ["/aividio-logo.png"],
+ },
+};
+
+export default function LandingLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children} ;
+}
diff --git a/frontend/src/app/landing/page.tsx b/frontend/src/app/landing/page.tsx
new file mode 100644
index 0000000..bb63ac0
--- /dev/null
+++ b/frontend/src/app/landing/page.tsx
@@ -0,0 +1,517 @@
+"use client";
+
+import { useState } from "react";
+import Link from "next/link";
+import Image from "next/image";
+import { Button } from "@/components/ui/button";
+import {
+ Sparkles,
+ Palette,
+ Upload,
+ Play,
+ ArrowRight,
+ ChevronDown,
+ Check,
+} from "lucide-react";
+import { HeroVisual } from "./components/hero-visual";
+import { BentoGrid } from "./components/bento-grid";
+import { StyleShowcase } from "./components/style-showcase";
+import { PricingSection } from "./components/pricing-card";
+
+/* ==========================================================================
+ Hero Section
+ ========================================================================== */
+
+function HeroSection() {
+ return (
+
+ {/* Background effects */}
+
+ {/* Radial green glow */}
+
+ {/* Dot grid pattern */}
+
+ {/* Secondary glow */}
+
+ {/* Bottom fade line */}
+
+
+
+
+ {/* Badge */}
+
+
+
+ Now with batch production -- create 10 videos at once
+
+
+
+ {/* Heading */}
+
+ Create YouTube Videos
+
+ with AI in Minutes
+
+
+ {/* Subheading */}
+
+ From topic to published video. AI writes the script, generates
+ visuals, adds voiceover, and produces a complete faceless YouTube
+ video — ready to upload.
+
+
+ {/* CTA Buttons */}
+
+
+ {/* Trust bar */}
+
+ No credit card required
+
+ 3 free videos
+
+ Cancel anytime
+
+
+ {/* Hero Visual / Dashboard Mockup */}
+
+
+
+ );
+}
+
+/* ==========================================================================
+ Logo / Social Proof Bar
+ ========================================================================== */
+
+const NICHE_LABELS = [
+ "Finance",
+ "Tech",
+ "Education",
+ "Health",
+ "Business",
+ "Crypto",
+ "Self-Improvement",
+ "True Crime",
+ "History",
+];
+
+function SocialProofBar() {
+ return (
+
+
+
+
+ Trusted by creators making videos for
+
+
+ {NICHE_LABELS.map((label) => (
+
+ {label}
+
+ ))}
+
+
+
+
+ );
+}
+
+/* ==========================================================================
+ How It Works
+ ========================================================================== */
+
+const STEPS = [
+ {
+ number: "01",
+ title: "Describe your video",
+ description:
+ "Enter any topic. AI generates a complete script with sections, hooks, and calls-to-action optimized for retention.",
+ icon: Sparkles,
+ },
+ {
+ number: "02",
+ title: "Choose your style",
+ description:
+ "Pick from 10 visual styles — oil paintings, cinematic, anime, and more. AI creates matching visuals for every scene.",
+ icon: Palette,
+ },
+ {
+ number: "03",
+ title: "Export & publish",
+ description:
+ "Download in 1080p or auto-publish to YouTube, TikTok, and Instagram. Complete with SEO-optimized metadata.",
+ icon: Upload,
+ },
+];
+
+function HowItWorksSection() {
+ return (
+
+
+ {/* Section header */}
+
+
+ How it works
+
+
+ Three steps to your first video
+
+
+ No editing skills required. No camera needed. Just your ideas.
+
+
+
+ {/* Steps */}
+
+ {STEPS.map((step) => (
+
+ {/* Step number */}
+
+ {step.number}
+
+
+ {/* Icon */}
+
+
+
+
+ {/* Content */}
+
+ {step.title}
+
+
+ {step.description}
+
+
+ ))}
+
+
+
+ );
+}
+
+/* ==========================================================================
+ FAQ Section
+ ========================================================================== */
+
+const FAQ_ITEMS = [
+ {
+ question: "How does the AI generate videos?",
+ answer:
+ "AIVIDIO uses a multi-step pipeline: GPT-4o writes a researched script, our visual engine matches each section with imagery (stock footage, oil paintings, or AI-generated scenes), a neural voice narrates the script, and our editor assembles everything with transitions, captions, and background music.",
+ },
+ {
+ question: "Can I edit the script before generating?",
+ answer:
+ "Absolutely. After the AI generates a script, you get a full editor where you can rewrite sections, adjust timing, change the tone, or add custom segments. You can also provide your own script from scratch.",
+ },
+ {
+ question: "How long does it take to produce a video?",
+ answer:
+ "A typical 8-10 minute video takes about 5-8 minutes to produce. Stock footage videos render fastest, while AI-generated cinematic scenes take slightly longer. Pro and Business users get priority rendering.",
+ },
+ {
+ question: "Do I own the videos I create?",
+ answer:
+ "Yes. All videos are yours to use commercially. Pro and Business plans include full commercial rights with no attribution required. Free plan videos include a small watermark.",
+ },
+ {
+ question: "What YouTube niches work best?",
+ answer:
+ "Finance, history, true crime, motivation, technology explainers, top-10 lists, educational content, and news commentary. Any niche that uses narration over visuals is a great fit.",
+ },
+ {
+ question: "How many visual styles are available?",
+ answer:
+ "10 styles: Oil Painting, Cinematic Realism, Anime, Watercolor, Dark Noir, Retro Vintage, Corporate Clean, Sci-Fi, Nature, and Stock Footage. Each is optimized for specific content types.",
+ },
+ {
+ question: "Can I use AIVIDIO for YouTube monetization?",
+ answer:
+ "Yes. Videos created with AIVIDIO are eligible for YouTube monetization. The content is unique, original, and meets YouTube's guidelines for AI-assisted content when properly disclosed.",
+ },
+ {
+ question: "Can I cancel anytime?",
+ answer:
+ "Yes. No contracts or cancellation fees. Cancel from your account settings anytime. You retain access until the end of your current billing period.",
+ },
+];
+
+function FAQItem({
+ question,
+ answer,
+}: {
+ question: string;
+ answer: string;
+}) {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+
setOpen(!open)}
+ aria-expanded={open}
+ >
+
+ {question}
+
+
+
+
+
+ );
+}
+
+function FAQSection() {
+ return (
+
+
+ {/* Section header */}
+
+
+ FAQ
+
+
+ Frequently asked questions
+
+
+
+ {/* FAQ list */}
+
+ {FAQ_ITEMS.map((item) => (
+
+ ))}
+
+
+
+ );
+}
+
+/* ==========================================================================
+ Final CTA Section
+ ========================================================================== */
+
+function CTASection() {
+ return (
+
+
+
+ {/* Background glow */}
+
+
+
+
+
+ Ready to create your first video?
+
+
+ Start creating free — no credit card required.
+
+
+
+ Get Started Free
+
+
+
+
+
+
+
+ );
+}
+
+/* ==========================================================================
+ Footer
+ ========================================================================== */
+
+const FOOTER_LINKS = {
+ Product: [
+ { label: "Features", href: "#features" },
+ { label: "Pricing", href: "#pricing" },
+ { label: "Changelog", href: "#" },
+ { label: "API Docs", href: "#" },
+ ],
+ Resources: [
+ { label: "Blog", href: "#" },
+ { label: "Tutorials", href: "#" },
+ { label: "Help Center", href: "#" },
+ { label: "Status", href: "#" },
+ ],
+ Company: [
+ { label: "About", href: "#" },
+ { label: "Careers", href: "#" },
+ { label: "Contact", href: "#" },
+ ],
+ Legal: [
+ { label: "Terms of Service", href: "#" },
+ { label: "Privacy Policy", href: "#" },
+ { label: "Cookie Policy", href: "#" },
+ ],
+};
+
+function Footer() {
+ return (
+
+
+
+ {/* Brand */}
+
+
+
+
+
+ AI-powered video creation for YouTube. From script to screen in
+ minutes.
+
+ {/* Social links */}
+
+
+
+ {/* Link columns */}
+ {Object.entries(FOOTER_LINKS).map(([heading, links]) => (
+
+ ))}
+
+
+ {/* Bottom bar */}
+
+
+ © 2025 AIVIDIO. All rights reserved.
+
+
+ Built with AI, for AI creators.
+
+
+
+
+ );
+}
+
+/* ==========================================================================
+ Landing Page — Full Assembly
+ ========================================================================== */
+
+export default function LandingPage() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
new file mode 100644
index 0000000..8731b17
--- /dev/null
+++ b/frontend/src/app/layout.tsx
@@ -0,0 +1,47 @@
+import type { Metadata } from "next";
+import { Inter } from "next/font/google";
+import "./globals.css";
+import { LayoutRouter } from "@/components/layout/layout-router";
+
+const inter = Inter({
+ subsets: ["latin"],
+ display: "swap",
+ variable: "--font-inter",
+});
+
+export const metadata: Metadata = {
+ title: "AIVIDIO — Create Faceless YouTube Videos with AI",
+ description:
+ "From topic to published video in minutes. AI writes, narrates, and produces professional videos.",
+ icons: {
+ icon: "/aividio-monogram.png",
+ },
+ openGraph: {
+ title: "AIVIDIO — Create Faceless YouTube Videos with AI",
+ description:
+ "From topic to published video in minutes. AI writes, narrates, and produces professional videos.",
+ images: [{ url: "/aividio-official.png" }],
+ type: "website",
+ },
+ twitter: {
+ card: "summary",
+ title: "AIVIDIO — Create Faceless YouTube Videos with AI",
+ description:
+ "From topic to published video in minutes. AI writes, narrates, and produces professional videos.",
+ images: ["/aividio-official.png"],
+ },
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx
new file mode 100644
index 0000000..3340067
--- /dev/null
+++ b/frontend/src/app/login/page.tsx
@@ -0,0 +1,140 @@
+"use client";
+
+import { useState, type FormEvent } from "react";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { useAuth } from "@/components/auth/auth-provider";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+
+export default function LoginPage() {
+ const router = useRouter();
+ const { login } = useAuth();
+
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ async function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+ setError("");
+ setIsSubmitting(true);
+
+ try {
+ await login(email, password);
+ router.push("/");
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Something went wrong");
+ } finally {
+ setIsSubmitting(false);
+ }
+ }
+
+ return (
+
+
+ {/* Logo */}
+
+
+
+ Ai
+
+
+
+ Sign in to AIVidio
+
+
+
+ {/* Card */}
+
+
+ {/* Footer link */}
+
+ Don't have an account?{" "}
+
+ Sign up
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/not-found.tsx b/frontend/src/app/not-found.tsx
new file mode 100644
index 0000000..f138ef9
--- /dev/null
+++ b/frontend/src/app/not-found.tsx
@@ -0,0 +1,23 @@
+import Link from "next/link";
+
+export default function NotFound() {
+ return (
+
+
+
404
+
+ Page not found
+
+
+ The page you are looking for does not exist or has been moved.
+
+
+ Back to Dashboard
+
+
+
+ );
+}
diff --git a/frontend/src/app/schedule/page.tsx b/frontend/src/app/schedule/page.tsx
new file mode 100644
index 0000000..fd54180
--- /dev/null
+++ b/frontend/src/app/schedule/page.tsx
@@ -0,0 +1,165 @@
+"use client";
+
+import { useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { UpcomingList } from "@/components/schedule/upcoming-list";
+import {
+ CalendarClock,
+ Plus,
+ Repeat,
+ Hash,
+ Clock,
+ Zap,
+} from "lucide-react";
+
+interface RecurringRule {
+ id: string;
+ channel: string;
+ frequency: string;
+ topicSource: string;
+ isActive: boolean;
+ nextRun: string;
+ videosGenerated: number;
+}
+
+const SAMPLE_RULES: RecurringRule[] = [
+ {
+ id: "1",
+ channel: "Tech Daily",
+ frequency: "Daily at 9:00 AM",
+ topicSource: "AI-suggested trending topics",
+ isActive: true,
+ nextRun: "2026-04-05T09:00:00Z",
+ videosGenerated: 47,
+ },
+ {
+ id: "2",
+ channel: "Dev Tutorials",
+ frequency: "3x per week (Mon, Wed, Fri)",
+ topicSource: "Content calendar queue",
+ isActive: true,
+ nextRun: "2026-04-07T10:00:00Z",
+ videosGenerated: 23,
+ },
+ {
+ id: "3",
+ channel: "Business Talks",
+ frequency: "Weekly on Tuesdays",
+ topicSource: "RSS feed: TechCrunch",
+ isActive: false,
+ nextRun: "2026-04-08T08:00:00Z",
+ videosGenerated: 12,
+ },
+];
+
+export default function SchedulePage() {
+ const [showForm, setShowForm] = useState(false);
+ const [rules] = useState(SAMPLE_RULES);
+
+ return (
+
+
+ {/* Page header */}
+
+
+
+
Schedule
+
+
setShowForm(!showForm)}
+ className="gap-2"
+ >
+
+ Schedule Video
+
+
+
+
+ {/* Upcoming section */}
+
+
+ {/* Recurring section */}
+
+
+
+
+ Recurring Rules
+
+
+
+
+ {rules.map((rule) => (
+
+ {/* Rule header */}
+
+
+
+ {rule.channel}
+
+
+ {rule.frequency}
+
+
+
+ {rule.isActive ? "Active" : "Paused"}
+
+
+
+ {/* Rule details */}
+
+
+
+
+
+ Topic Source
+
+
+ {rule.topicSource}
+
+
+
+
+
+
+
Generated
+
+ {rule.videosGenerated} videos
+
+
+
+
+
+ {/* Next run */}
+
+
Next run
+
+ {new Date(rule.nextRun).toLocaleDateString("en-US", {
+ weekday: "short",
+ month: "short",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx
new file mode 100644
index 0000000..32eb227
--- /dev/null
+++ b/frontend/src/app/settings/page.tsx
@@ -0,0 +1,429 @@
+"use client";
+
+import { useState, useCallback } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+import {
+ Settings,
+ Key,
+ Bell,
+ DollarSign,
+ Webhook,
+ Plus,
+ Trash2,
+ Eye,
+ EyeOff,
+ Copy,
+ Check,
+} from "lucide-react";
+
+/* ------------------------------------------------------------------ */
+/* Types */
+/* ------------------------------------------------------------------ */
+
+interface ApiKey {
+ id: string;
+ prefix: string;
+ createdAt: string;
+ lastUsed: string | null;
+}
+
+interface WebhookEntry {
+ id: string;
+ url: string;
+ events: string[];
+ isActive: boolean;
+}
+
+/* ------------------------------------------------------------------ */
+/* Sample Data */
+/* ------------------------------------------------------------------ */
+
+const SAMPLE_KEYS: ApiKey[] = [
+ {
+ id: "1",
+ prefix: "vm_live_abc1",
+ createdAt: "2026-03-15T10:00:00Z",
+ lastUsed: "2026-04-04T08:30:00Z",
+ },
+ {
+ id: "2",
+ prefix: "vm_test_xyz9",
+ createdAt: "2026-04-01T14:00:00Z",
+ lastUsed: null,
+ },
+];
+
+const SAMPLE_WEBHOOKS: WebhookEntry[] = [
+ {
+ id: "1",
+ url: "https://hooks.slack.com/services/T00/B00/xxxx",
+ events: ["video.completed", "video.failed"],
+ isActive: true,
+ },
+ {
+ id: "2",
+ url: "https://discord.com/api/webhooks/1234/abcd",
+ events: ["video.completed"],
+ isActive: true,
+ },
+];
+
+/* ------------------------------------------------------------------ */
+/* Section wrapper */
+/* ------------------------------------------------------------------ */
+
+function SettingsSection({
+ icon: Icon,
+ title,
+ description,
+ children,
+}: {
+ icon: typeof Key;
+ title: string;
+ description: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+
+
{title}
+
+
{description}
+
+
{children}
+
+ );
+}
+
+/* ------------------------------------------------------------------ */
+/* Toggle component */
+/* ------------------------------------------------------------------ */
+
+function Toggle({
+ checked,
+ onChange,
+ label,
+}: {
+ checked: boolean;
+ onChange: (val: boolean) => void;
+ label: string;
+}) {
+ return (
+ onChange(!checked)}
+ className={cn(
+ "relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors duration-200",
+ checked ? "bg-[#10a37f]" : "bg-white/[0.12]"
+ )}
+ >
+
+
+ );
+}
+
+/* ------------------------------------------------------------------ */
+/* Page */
+/* ------------------------------------------------------------------ */
+
+export default function SettingsPage() {
+ const [keys] = useState(SAMPLE_KEYS);
+ const [webhooks] = useState(SAMPLE_WEBHOOKS);
+
+ // Notification toggles
+ const [emailEnabled, setEmailEnabled] = useState(true);
+ const [discordEnabled, setDiscordEnabled] = useState(true);
+ const [slackEnabled, setSlackEnabled] = useState(false);
+ const [discordWebhook, setDiscordWebhook] = useState(
+ "https://discord.com/api/webhooks/..."
+ );
+ const [slackWebhook, setSlackWebhook] = useState("");
+
+ // Budget
+ const [monthlyBudget, setMonthlyBudget] = useState("50.00");
+ const currentSpend = 23.47;
+
+ // Webhook form
+ const [newWebhookUrl, setNewWebhookUrl] = useState("");
+
+ // Clipboard
+ const [copiedId, setCopiedId] = useState(null);
+ const handleCopy = useCallback((text: string, id: string) => {
+ navigator.clipboard.writeText(text);
+ setCopiedId(id);
+ setTimeout(() => setCopiedId(null), 2000);
+ }, []);
+
+ const safeBudget = Math.max(0, parseFloat(monthlyBudget || "0"));
+ const budgetPct = safeBudget > 0
+ ? Math.min((currentSpend / safeBudget) * 100, 100)
+ : 0;
+
+ return (
+
+
+ {/* Page header */}
+
+
+
Settings
+
+
+
+ {/* ---- API Keys ---- */}
+
+
+ {keys.map((key) => (
+
+
+ {key.prefix}...
+
+
+
+ Created{" "}
+ {new Date(key.createdAt).toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ })}
+
+
handleCopy(key.prefix, key.id)}
+ className="flex h-7 w-7 items-center justify-center rounded-md text-[#666] transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#999]"
+ aria-label="Copy key prefix"
+ >
+ {copiedId === key.id ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ ))}
+
+
+
+ Create Key
+
+
+
+ {/* ---- Notifications ---- */}
+
+
+ {/* Email */}
+
+
+
Email
+
+ Receive email notifications for completed and failed jobs
+
+
+
+
+
+ {/* Discord */}
+
+
+
+
Discord
+
+ Post updates to a Discord channel
+
+
+
+
+ {discordEnabled && (
+
setDiscordWebhook(e.target.value)}
+ placeholder="https://discord.com/api/webhooks/..."
+ className="text-xs"
+ />
+ )}
+
+
+ {/* Slack */}
+
+
+
+
Slack
+
+ Post updates to a Slack channel
+
+
+
+
+ {slackEnabled && (
+
setSlackWebhook(e.target.value)}
+ placeholder="https://hooks.slack.com/services/..."
+ className="text-xs"
+ />
+ )}
+
+
+
+
+ {/* ---- Budget ---- */}
+
+
+
+
+
+ Monthly limit (USD)
+
+
+
+ $
+
+ setMonthlyBudget(e.target.value)}
+ className="pl-7 tabular-nums"
+ min="0"
+ step="5"
+ />
+
+
+
+
+ {/* Spend bar */}
+
+
+ Current spend
+
+ ${currentSpend.toFixed(2)}{" "}
+
+ / ${parseFloat(monthlyBudget || "0").toFixed(2)}
+
+
+
+
+
90 ? "bg-[#ef4444]" : budgetPct > 70 ? "bg-[#f59e0b]" : "bg-[#10a37f]"
+ )}
+ style={{ width: `${budgetPct}%` }}
+ />
+
+
+ {budgetPct.toFixed(0)}% of monthly budget used
+
+
+
+
+
+ {/* ---- Webhooks ---- */}
+
+
+ {webhooks.map((wh) => (
+
+
+
+ {wh.url}
+
+
+ {wh.events.map((ev) => (
+
+ {ev}
+
+ ))}
+
+
+
+ {wh.isActive ? "Active" : "Inactive"}
+
+
+
+
+
+ ))}
+
+
+ {/* Add new webhook */}
+
+
setNewWebhookUrl(e.target.value)}
+ placeholder="https://your-endpoint.com/webhook"
+ className="flex-1 text-xs font-mono"
+ />
+
+
+ Add
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/signup/page.tsx b/frontend/src/app/signup/page.tsx
new file mode 100644
index 0000000..a3642a6
--- /dev/null
+++ b/frontend/src/app/signup/page.tsx
@@ -0,0 +1,250 @@
+"use client";
+
+import { useState, useMemo, type FormEvent } from "react";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { useAuth } from "@/components/auth/auth-provider";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+
+// ---------------------------------------------------------------------------
+// Password strength
+// ---------------------------------------------------------------------------
+
+interface StrengthResult {
+ score: number; // 0-4
+ label: string;
+ color: string;
+}
+
+function evaluatePassword(password: string): StrengthResult {
+ let score = 0;
+ if (password.length >= 8) score++;
+ if (password.length >= 12) score++;
+ if (/[A-Z]/.test(password) && /[a-z]/.test(password)) score++;
+ if (/\d/.test(password)) score++;
+ if (/[^A-Za-z0-9]/.test(password)) score++;
+
+ // Cap at 4
+ score = Math.min(score, 4);
+
+ const map: Record
= {
+ 0: { label: "Too weak", color: "#ef4444" },
+ 1: { label: "Weak", color: "#ef4444" },
+ 2: { label: "Fair", color: "#f59e0b" },
+ 3: { label: "Good", color: "#10a37f" },
+ 4: { label: "Strong", color: "#10a37f" },
+ };
+
+ return { score, ...map[score] };
+}
+
+// ---------------------------------------------------------------------------
+// Page
+// ---------------------------------------------------------------------------
+
+export default function SignupPage() {
+ const router = useRouter();
+ const { signup } = useAuth();
+
+ const [name, setName] = useState("");
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
+ const [error, setError] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const strength = useMemo(() => evaluatePassword(password), [password]);
+
+ async function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+ setError("");
+
+ if (password !== confirmPassword) {
+ setError("Passwords do not match");
+ return;
+ }
+
+ if (strength.score < 2) {
+ setError(
+ "Please choose a stronger password (at least 8 characters with a mix of cases and numbers)"
+ );
+ return;
+ }
+
+ setIsSubmitting(true);
+
+ try {
+ await signup(name, email, password);
+ router.push("/");
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Something went wrong");
+ } finally {
+ setIsSubmitting(false);
+ }
+ }
+
+ return (
+
+
+ {/* Logo */}
+
+
+
+ Ai
+
+
+
+ Create your account
+
+
+
+ {/* Card */}
+
+
+ {/* Footer link */}
+
+ Already have an account?{" "}
+
+ Sign in
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/templates/page.tsx b/frontend/src/app/templates/page.tsx
new file mode 100644
index 0000000..f92120e
--- /dev/null
+++ b/frontend/src/app/templates/page.tsx
@@ -0,0 +1,220 @@
+"use client";
+
+import { useState } from "react";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import { PageHeader } from "@/components/shared/page-header";
+import {
+ TemplateCard,
+ type VideoTemplate,
+} from "@/components/templates/template-card";
+import { ArrowRight, Sparkles } from "lucide-react";
+
+const TEMPLATES: VideoTemplate[] = [
+ {
+ id: "dark-cinematic",
+ name: "Dark Cinematic",
+ description:
+ "Dramatic storytelling with bold white captions and film-grade transitions. Perfect for documentaries and deep dives.",
+ accent: "#3b82f6",
+ captionStyle: {
+ text: "The untold story begins here...",
+ position: "bottom-center",
+ fontWeight: "700",
+ fontSize: "14px",
+ },
+ tags: {
+ transition: "Cinematic Cuts",
+ music: "Cinematic",
+ captionAnimation: "Fade In",
+ },
+ },
+ {
+ id: "clean-educational",
+ name: "Clean Educational",
+ description:
+ "Minimalist design that keeps the focus on your content. Clean subtitles and calm background music.",
+ accent: "#10b981",
+ captionStyle: {
+ text: "Step 1: Understanding the basics",
+ position: "bottom-center",
+ fontWeight: "400",
+ fontSize: "12px",
+ },
+ tags: {
+ transition: "Smooth Fade",
+ music: "Ambient",
+ captionAnimation: "Typewriter",
+ },
+ },
+ {
+ id: "tiktok-viral",
+ name: "TikTok Viral",
+ description:
+ "Eye-catching neon bold captions with punchy animations. Built to stop the scroll and go viral.",
+ accent: "#f43f5e",
+ captionStyle: {
+ text: "YOU WON'T BELIEVE THIS",
+ position: "center",
+ fontWeight: "900",
+ fontSize: "16px",
+ },
+ tags: {
+ transition: "Zoom Flash",
+ music: "Electronic",
+ captionAnimation: "Pop In",
+ },
+ },
+ {
+ id: "storytelling",
+ name: "Storytelling",
+ description:
+ "Warm, narrative-driven style with elegant serif captions and gentle fade transitions. For long-form stories.",
+ accent: "#f59e0b",
+ captionStyle: {
+ text: "Once upon a midnight hour...",
+ position: "bottom-center",
+ fontWeight: "400",
+ fontStyle: "italic",
+ fontSize: "13px",
+ fontFamily: "Georgia, serif",
+ },
+ tags: {
+ transition: "Fade",
+ music: "Cinematic",
+ captionAnimation: "Slide Up",
+ },
+ },
+ {
+ id: "finance-business",
+ name: "Finance / Business",
+ description:
+ "Professional dark theme with green accent data overlays. Clean, authoritative, and trustworthy.",
+ accent: "#10a37f",
+ captionStyle: {
+ text: "Markets rallied 2.4% today",
+ position: "bottom-center",
+ fontWeight: "500",
+ fontSize: "12px",
+ fontFamily: "system-ui, sans-serif",
+ },
+ tags: {
+ transition: "Slide",
+ music: "Lo-Fi",
+ captionAnimation: "Word Highlight",
+ },
+ },
+ {
+ id: "motivation",
+ name: "Motivation",
+ description:
+ "High-contrast, large karaoke-style captions that hit hard. Designed for gym clips and motivational content.",
+ accent: "#8b5cf6",
+ captionStyle: {
+ text: "NEVER GIVE UP",
+ position: "center",
+ fontWeight: "900",
+ fontSize: "18px",
+ },
+ tags: {
+ transition: "Hard Cut",
+ music: "Electronic",
+ captionAnimation: "Karaoke",
+ },
+ },
+ {
+ id: "podcast-style",
+ name: "Podcast Style",
+ description:
+ "Minimal, relaxed layout with bottom-left subtitles. Feels like a conversation, ideal for podcast clips.",
+ accent: "#6b7280",
+ captionStyle: {
+ text: "...and that changed everything.",
+ position: "bottom-left",
+ fontWeight: "400",
+ fontSize: "12px",
+ },
+ tags: {
+ transition: "Crossfade",
+ music: "Chill",
+ captionAnimation: "Fade",
+ },
+ },
+];
+
+export default function TemplatesPage() {
+ const [selectedId, setSelectedId] = useState(null);
+
+ const selectedTemplate = TEMPLATES.find((t) => t.id === selectedId);
+
+ return (
+
+
+
+ {/* Template grid */}
+
+ {TEMPLATES.map((template) => (
+
+ setSelectedId((prev) =>
+ prev === template.id ? null : template.id
+ )
+ }
+ />
+ ))}
+
+
+ {/* Sticky bottom bar when a template is selected */}
+
+
+
+
+ {selectedTemplate && (
+ <>
+
+
+
+ {selectedTemplate.name}
+
+
+ {selectedTemplate.tags.music} ·{" "}
+ {selectedTemplate.tags.transition} ·{" "}
+ {selectedTemplate.tags.captionAnimation}
+
+
+ >
+ )}
+
+
+
+
+ Create Video with this Style
+
+
+
+
+
+
+
+ {/* Bottom spacer so cards aren't hidden behind the sticky bar */}
+ {selectedTemplate &&
}
+
+ );
+}
diff --git a/frontend/src/app/videos/page.tsx b/frontend/src/app/videos/page.tsx
new file mode 100644
index 0000000..fcf99ba
--- /dev/null
+++ b/frontend/src/app/videos/page.tsx
@@ -0,0 +1,38 @@
+import { Suspense } from "react";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import { VideoListContent } from "@/components/videos/video-list-content";
+
+export default function VideosPage({
+ searchParams,
+}: {
+ searchParams: Promise<{ status?: string }>;
+}) {
+ return (
+
+ {/* Header */}
+
+
Videos
+
+ New Video
+
+
+
+ {/* Content with filter tabs and table */}
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+ }
+ >
+
+
+
+ );
+}
diff --git a/frontend/src/app/voices/page.tsx b/frontend/src/app/voices/page.tsx
new file mode 100644
index 0000000..4f970b9
--- /dev/null
+++ b/frontend/src/app/voices/page.tsx
@@ -0,0 +1,140 @@
+"use client";
+
+import { useState, useMemo } from "react";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import { VoiceCard } from "@/components/voices/voice-card";
+import type { Voice, VoiceProvider } from "@/components/voices/voice-card";
+import { Mic, Plus, AudioLines } from "lucide-react";
+
+const SAMPLE_VOICES: Voice[] = [
+ {
+ id: "1",
+ name: "Alex Narrator",
+ provider: "elevenlabs",
+ isCloned: false,
+ usageCount: 342,
+ },
+ {
+ id: "2",
+ name: "Sarah Tech",
+ provider: "elevenlabs",
+ isCloned: true,
+ usageCount: 187,
+ },
+ {
+ id: "3",
+ name: "Deep Baritone",
+ provider: "replicate",
+ isCloned: false,
+ usageCount: 95,
+ },
+ {
+ id: "4",
+ name: "Connor Custom",
+ provider: "fal",
+ isCloned: true,
+ usageCount: 28,
+ },
+ {
+ id: "5",
+ name: "Studio Pro",
+ provider: "elevenlabs",
+ isCloned: false,
+ usageCount: 451,
+ },
+ {
+ id: "6",
+ name: "Warm Storyteller",
+ provider: "replicate",
+ isCloned: true,
+ usageCount: 63,
+ },
+];
+
+type FilterOption = "all" | VoiceProvider;
+
+const FILTER_OPTIONS: { value: FilterOption; label: string }[] = [
+ { value: "all", label: "All" },
+ { value: "elevenlabs", label: "ElevenLabs" },
+ { value: "replicate", label: "Replicate" },
+ { value: "fal", label: "fal" },
+];
+
+export default function VoicesPage() {
+ const [filter, setFilter] = useState("all");
+ const [voices] = useState(SAMPLE_VOICES);
+
+ const filteredVoices = useMemo(() => {
+ if (filter === "all") return voices;
+ return voices.filter((v) => v.provider === filter);
+ }, [voices, filter]);
+
+ return (
+
+
+ {/* Page header */}
+
+
+
+
+
+ Clone Voice
+
+
+
+
+ {/* Filters */}
+
+ {FILTER_OPTIONS.map((option) => (
+ setFilter(option.value)}
+ className={`rounded-lg px-3.5 py-1.5 text-xs font-medium transition-all duration-150 ${
+ filter === option.value
+ ? "bg-white/[0.08] text-[#ececec]"
+ : "text-[#666] hover:bg-white/[0.04] hover:text-[#999]"
+ }`}
+ >
+ {option.label}
+
+ ))}
+
+ {filteredVoices.length} voice{filteredVoices.length !== 1 ? "s" : ""}
+
+
+
+ {/* Voice grid */}
+ {filteredVoices.length > 0 ? (
+
+ {filteredVoices.map((voice) => (
+
+ ))}
+
+ ) : (
+ /* Empty state */
+
+
+
+
+
No voices found
+
+ {filter === "all"
+ ? "Clone a voice or add one from a provider to get started"
+ : `No voices from ${FILTER_OPTIONS.find((o) => o.value === filter)?.label}`}
+
+
+
+
+ Clone Voice
+
+
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/brand/logo.tsx b/frontend/src/components/brand/logo.tsx
new file mode 100644
index 0000000..a12a07d
--- /dev/null
+++ b/frontend/src/components/brand/logo.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import Image from "next/image";
+import { cn } from "@/lib/utils";
+
+interface LogoProps {
+ size?: "sm" | "md" | "lg" | "xl";
+ showText?: boolean;
+ variant?: "light" | "dark";
+ className?: string;
+}
+
+const sizes = {
+ sm: { width: 90, height: 24, iconSize: 24 },
+ md: { width: 120, height: 32, iconSize: 32 },
+ lg: { width: 160, height: 42, iconSize: 42 },
+ xl: { width: 200, height: 52, iconSize: 52 },
+};
+
+export function Logo({
+ size = "md",
+ showText = true,
+ variant = "dark",
+ className,
+}: LogoProps) {
+ const s = sizes[size];
+ const invert = variant === "dark";
+
+ if (!showText) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/components/channels/channel-card.tsx b/frontend/src/components/channels/channel-card.tsx
new file mode 100644
index 0000000..33af4bc
--- /dev/null
+++ b/frontend/src/components/channels/channel-card.tsx
@@ -0,0 +1,78 @@
+"use client";
+
+import Link from "next/link";
+import type { Channel } from "@/types";
+import { Card, CardContent } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { StatusDot } from "@/components/shared/status-dot";
+
+function getMonogram(name: string): string {
+ return name
+ .split(/\s+/)
+ .map((w) => w[0])
+ .join("")
+ .toUpperCase()
+ .slice(0, 2);
+}
+
+function getMonogramColor(name: string): string {
+ const colors = [
+ "bg-[#10a37f]/20 text-[#10a37f]",
+ "bg-[#6366f1]/20 text-[#6366f1]",
+ "bg-[#f59e0b]/20 text-[#f59e0b]",
+ "bg-[#ef4444]/20 text-[#ef4444]",
+ "bg-[#ec4899]/20 text-[#ec4899]",
+ "bg-[#8b5cf6]/20 text-[#8b5cf6]",
+ ];
+ let hash = 0;
+ for (let i = 0; i < name.length; i++) {
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ return colors[Math.abs(hash) % colors.length];
+}
+
+interface ChannelCardProps {
+ channel: Channel;
+}
+
+export function ChannelCard({ channel }: ChannelCardProps) {
+ return (
+
+
+
+
+
+ {getMonogram(channel.name)}
+
+
+
+
+
+ {channel.name}
+
+
+
+
+
+ {channel.profile_path}
+
+
+
+ {channel.youtube_channel_id ? (
+ YouTube linked
+ ) : (
+ Not linked
+ )}
+
+ {channel.is_active ? "Active" : "Inactive"}
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/jobs/job-card.tsx b/frontend/src/components/jobs/job-card.tsx
new file mode 100644
index 0000000..5fa02c6
--- /dev/null
+++ b/frontend/src/components/jobs/job-card.tsx
@@ -0,0 +1,117 @@
+"use client";
+
+import Link from "next/link";
+import { RotateCw } from "lucide-react";
+import type { Job, JobStatus, JobType } from "@/types";
+import { Card, CardContent } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { StatusDot } from "@/components/shared/status-dot";
+import { formatRelativeTime, truncate } from "@/lib/utils";
+
+const STATUS_BADGE: Record = {
+ queued: "warning",
+ running: "info",
+ completed: "success",
+ failed: "error",
+ cancelled: "default",
+};
+
+const STATUS_LABEL: Record = {
+ queued: "Queued",
+ running: "Running",
+ completed: "Completed",
+ failed: "Failed",
+ cancelled: "Cancelled",
+};
+
+const JOB_TYPE_LABELS: Record = {
+ full_pipeline: "Full Pipeline",
+ script_only: "Script Generation",
+ tts_only: "Text-to-Speech",
+ video_only: "Video Render",
+ upload_only: "Upload",
+ thumbnail_only: "Thumbnail",
+};
+
+interface JobCardProps {
+ job: Job;
+ onRetry?: (id: string) => void;
+}
+
+export function JobCard({ job, onRetry }: JobCardProps) {
+ return (
+
+
+
+
+
+
+
+
+ {STATUS_LABEL[job.status]}
+
+
+ {JOB_TYPE_LABELS[job.job_type]}
+
+
+
+ {job.video?.topic_prompt && (
+
+ {truncate(job.video.topic_prompt, 80)}
+
+ )}
+
+ {job.current_stage && (
+
+ Stage: {job.current_stage}
+
+ )}
+
+ {(job.status === "running" || job.status === "queued") &&
+ job.progress_pct > 0 && (
+
+
+ Progress
+ {Math.round(job.progress_pct)}%
+
+
+
+ )}
+
+
+ {truncate(job.id, 12)}
+ {job.started_at && (
+
+ {formatRelativeTime(job.started_at)}
+
+ )}
+
+
+
+ {job.status === "failed" && onRetry && (
+
{
+ e.preventDefault();
+ e.stopPropagation();
+ onRetry(job.id);
+ }}
+ aria-label="Retry job"
+ className="shrink-0"
+ >
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/shared/empty-state.tsx b/frontend/src/components/shared/empty-state.tsx
new file mode 100644
index 0000000..f885159
--- /dev/null
+++ b/frontend/src/components/shared/empty-state.tsx
@@ -0,0 +1,34 @@
+import { cn } from "@/lib/utils";
+import type { LucideIcon } from "lucide-react";
+
+interface EmptyStateProps {
+ icon: LucideIcon;
+ title: string;
+ description: string;
+ action?: React.ReactNode;
+ className?: string;
+}
+
+export function EmptyState({
+ icon: Icon,
+ title,
+ description,
+ action,
+ className,
+}: EmptyStateProps) {
+ return (
+
+
+
+
+
{title}
+
{description}
+ {action &&
{action}
}
+
+ );
+}
diff --git a/frontend/src/components/shared/page-header.tsx b/frontend/src/components/shared/page-header.tsx
new file mode 100644
index 0000000..c3b3819
--- /dev/null
+++ b/frontend/src/components/shared/page-header.tsx
@@ -0,0 +1,26 @@
+import { cn } from "@/lib/utils";
+
+interface PageHeaderProps {
+ title: string;
+ description?: string;
+ action?: React.ReactNode;
+ className?: string;
+}
+
+export function PageHeader({ title, description, action, className }: PageHeaderProps) {
+ return (
+
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
+
+ {action &&
{action}
}
+
+ );
+}
diff --git a/frontend/src/components/shared/stat-card.tsx b/frontend/src/components/shared/stat-card.tsx
new file mode 100644
index 0000000..750664a
--- /dev/null
+++ b/frontend/src/components/shared/stat-card.tsx
@@ -0,0 +1,47 @@
+import { cn } from "@/lib/utils";
+import { TrendingUp, TrendingDown } from "lucide-react";
+
+interface StatCardProps {
+ label: string;
+ value: string | number;
+ trend?: {
+ value: number;
+ direction: "up" | "down";
+ };
+ className?: string;
+}
+
+export function StatCard({ label, value, trend, className }: StatCardProps) {
+ return (
+
+
+ {label}
+
+
+
+ {value}
+
+ {trend && (
+
+ {trend.direction === "up" ? (
+
+ ) : (
+
+ )}
+ {trend.value}%
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/shared/status-dot.tsx b/frontend/src/components/shared/status-dot.tsx
new file mode 100644
index 0000000..720d877
--- /dev/null
+++ b/frontend/src/components/shared/status-dot.tsx
@@ -0,0 +1,41 @@
+import { cn } from "@/lib/utils";
+
+type Status = "uploaded" | "generating" | "failed" | "ready" | "queued" | "draft" | "running" | "completed" | "cancelled";
+
+const statusConfig: Record = {
+ uploaded: { color: "bg-[#10a37f]", label: "Uploaded" },
+ generating: { color: "bg-[#6366f1]", pulse: true, label: "Generating" },
+ running: { color: "bg-[#6366f1]", pulse: true, label: "Running" },
+ failed: { color: "bg-[#ef4444]", label: "Failed" },
+ ready: { color: "bg-[#f59e0b]", label: "Ready" },
+ queued: { color: "bg-[#f59e0b]", label: "Queued" },
+ completed: { color: "bg-[#10a37f]", label: "Completed" },
+ cancelled: { color: "bg-[#666]", label: "Cancelled" },
+ draft: { color: "bg-[#666]", label: "Draft" },
+};
+
+interface StatusDotProps {
+ status: Status;
+ showLabel?: boolean;
+ className?: string;
+}
+
+export function StatusDot({ status, showLabel = false, className }: StatusDotProps) {
+ const config = statusConfig[status] || statusConfig.draft;
+
+ return (
+
+
+ {showLabel && (
+ {config.label}
+ )}
+
+ );
+}
diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx
new file mode 100644
index 0000000..a2115a2
--- /dev/null
+++ b/frontend/src/components/ui/badge.tsx
@@ -0,0 +1,45 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/lib/utils";
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium transition-colors",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-white/[0.06] text-[#ececec]",
+ success:
+ "bg-[#10a37f]/12 text-[#10a37f]",
+ warning:
+ "bg-[#f59e0b]/12 text-[#f59e0b]",
+ error:
+ "bg-[#ef4444]/12 text-[#ef4444]",
+ info:
+ "bg-[#6366f1]/12 text-[#6366f1]",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+);
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+const Badge = React.forwardRef(
+ ({ className, variant, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+Badge.displayName = "Badge";
+
+export { Badge, badgeVariants };
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx
new file mode 100644
index 0000000..f6a7108
--- /dev/null
+++ b/frontend/src/components/ui/button.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl text-sm font-medium transition-all duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#10a37f] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0d0d0d] disabled:pointer-events-none disabled:opacity-40 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-[#10a37f] text-white hover:bg-[#1a7f64] active:scale-[0.98]",
+ secondary:
+ "border border-white/[0.08] bg-transparent text-[#ececec] hover:border-white/[0.15] hover:bg-white/[0.04]",
+ ghost:
+ "text-[#999] hover:text-[#ececec] hover:bg-white/[0.06]",
+ destructive:
+ "bg-[#ef4444]/10 text-[#ef4444] hover:bg-[#ef4444]/20 active:scale-[0.98]",
+ link:
+ "text-[#10a37f] underline-offset-4 hover:underline p-0 h-auto",
+ },
+ size: {
+ sm: "h-8 px-3 text-xs rounded-lg",
+ default: "h-10 px-4 text-sm",
+ lg: "h-12 px-6 text-sm",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean;
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+ return (
+
+ );
+ }
+);
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx
new file mode 100644
index 0000000..f578f0e
--- /dev/null
+++ b/frontend/src/components/ui/card.tsx
@@ -0,0 +1,78 @@
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+Card.displayName = "Card";
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardHeader.displayName = "CardHeader";
+
+const CardTitle = React.forwardRef<
+ HTMLHeadingElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardTitle.displayName = "CardTitle";
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardDescription.displayName = "CardDescription";
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardContent.displayName = "CardContent";
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardFooter.displayName = "CardFooter";
+
+export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..088610b
--- /dev/null
+++ b/frontend/src/components/ui/dialog.tsx
@@ -0,0 +1,115 @@
+"use client";
+
+import * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { X } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+const Dialog = DialogPrimitive.Root;
+const DialogTrigger = DialogPrimitive.Trigger;
+const DialogPortal = DialogPrimitive.Portal;
+const DialogClose = DialogPrimitive.Close;
+
+const DialogOverlay = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
+
+const DialogContent = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+));
+DialogContent.displayName = DialogPrimitive.Content.displayName;
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+DialogHeader.displayName = "DialogHeader";
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+DialogFooter.displayName = "DialogFooter";
+
+const DialogTitle = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogTitle.displayName = DialogPrimitive.Title.displayName;
+
+const DialogDescription = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogDescription.displayName = DialogPrimitive.Description.displayName;
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+};
diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx
new file mode 100644
index 0000000..ccd5fa9
--- /dev/null
+++ b/frontend/src/components/ui/input.tsx
@@ -0,0 +1,22 @@
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+const Input = React.forwardRef<
+ HTMLInputElement,
+ React.InputHTMLAttributes
+>(({ className, type, ...props }, ref) => {
+ return (
+
+ );
+});
+Input.displayName = "Input";
+
+export { Input };
diff --git a/frontend/src/components/ui/progress.tsx b/frontend/src/components/ui/progress.tsx
new file mode 100644
index 0000000..637f6b1
--- /dev/null
+++ b/frontend/src/components/ui/progress.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import * as React from "react";
+import * as ProgressPrimitive from "@radix-ui/react-progress";
+import { cn } from "@/lib/utils";
+
+const Progress = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, value, ...props }, ref) => (
+
+
+
+));
+Progress.displayName = ProgressPrimitive.Root.displayName;
+
+export { Progress };
diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx
new file mode 100644
index 0000000..bafbe98
--- /dev/null
+++ b/frontend/src/components/ui/select.tsx
@@ -0,0 +1,117 @@
+"use client";
+
+import * as React from "react";
+import * as SelectPrimitive from "@radix-ui/react-select";
+import { ChevronDown, Check } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+const Select = SelectPrimitive.Root;
+const SelectGroup = SelectPrimitive.Group;
+const SelectValue = SelectPrimitive.Value;
+
+const SelectTrigger = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+ span]:line-clamp-1",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+
+));
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
+
+const SelectContent = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, position = "popper", ...props }, ref) => (
+
+
+
+ {children}
+
+
+
+));
+SelectContent.displayName = SelectPrimitive.Content.displayName;
+
+const SelectItem = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+));
+SelectItem.displayName = SelectPrimitive.Item.displayName;
+
+const SelectLabel = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SelectLabel.displayName = SelectPrimitive.Label.displayName;
+
+const SelectSeparator = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
+
+export {
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectItem,
+ SelectLabel,
+ SelectSeparator,
+};
diff --git a/frontend/src/components/ui/table.tsx b/frontend/src/components/ui/table.tsx
new file mode 100644
index 0000000..4ae6793
--- /dev/null
+++ b/frontend/src/components/ui/table.tsx
@@ -0,0 +1,87 @@
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+const Table = React.forwardRef<
+ HTMLTableElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+Table.displayName = "Table";
+
+const TableHeader = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableHeader.displayName = "TableHeader";
+
+const TableBody = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableBody.displayName = "TableBody";
+
+const TableRow = React.forwardRef<
+ HTMLTableRowElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableRow.displayName = "TableRow";
+
+const TableHead = React.forwardRef<
+ HTMLTableCellElement,
+ React.ThHTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableHead.displayName = "TableHead";
+
+const TableCell = React.forwardRef<
+ HTMLTableCellElement,
+ React.TdHTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableCell.displayName = "TableCell";
+
+export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell };
diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..599c43f
--- /dev/null
+++ b/frontend/src/components/ui/tabs.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+import { cn } from "@/lib/utils";
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/frontend/src/components/ui/textarea.tsx b/frontend/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..77004a2
--- /dev/null
+++ b/frontend/src/components/ui/textarea.tsx
@@ -0,0 +1,21 @@
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+const Textarea = React.forwardRef<
+ HTMLTextAreaElement,
+ React.TextareaHTMLAttributes
+>(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
+Textarea.displayName = "Textarea";
+
+export { Textarea };
diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..e9e8d1e
--- /dev/null
+++ b/frontend/src/components/ui/tooltip.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import * as React from "react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import { cn } from "@/lib/utils";
+
+const TooltipProvider = TooltipPrimitive.Provider;
+const Tooltip = TooltipPrimitive.Root;
+const TooltipTrigger = TooltipPrimitive.Trigger;
+
+const TooltipContent = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 6, ...props }, ref) => (
+
+
+
+));
+TooltipContent.displayName = TooltipPrimitive.Content.displayName;
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/frontend/src/components/voices/voice-card.tsx b/frontend/src/components/voices/voice-card.tsx
new file mode 100644
index 0000000..fa8ea57
--- /dev/null
+++ b/frontend/src/components/voices/voice-card.tsx
@@ -0,0 +1,151 @@
+"use client";
+
+import { useState, useCallback, useRef, useEffect } from "react";
+import { cn } from "@/lib/utils";
+import { Badge } from "@/components/ui/badge";
+import { Play, Pause, Mic } from "lucide-react";
+
+type VoiceProvider = "elevenlabs" | "replicate" | "fal";
+
+interface Voice {
+ id: string;
+ name: string;
+ provider: VoiceProvider;
+ isCloned: boolean;
+ usageCount: number;
+ previewUrl?: string;
+}
+
+const PROVIDER_CONFIG: Record<
+ VoiceProvider,
+ { label: string; className: string }
+> = {
+ elevenlabs: {
+ label: "ElevenLabs",
+ className: "bg-[#6366f1]/12 text-[#6366f1]",
+ },
+ replicate: {
+ label: "Replicate",
+ className: "bg-[#f59e0b]/12 text-[#f59e0b]",
+ },
+ fal: {
+ label: "fal",
+ className: "bg-[#10a37f]/12 text-[#10a37f]",
+ },
+};
+
+interface VoiceCardProps {
+ voice: Voice;
+ className?: string;
+}
+
+export function VoiceCard({ voice, className }: VoiceCardProps) {
+ const [isPlaying, setIsPlaying] = useState(false);
+ const timeoutRef = useRef | null>(null);
+
+ const handlePlayToggle = useCallback(() => {
+ setIsPlaying((prev) => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ timeoutRef.current = null;
+ }
+ if (!prev) {
+ // Starting playback -- auto-stop after 3s
+ timeoutRef.current = setTimeout(() => setIsPlaying(false), 3000);
+ }
+ return !prev;
+ });
+ }, []);
+
+ // Cleanup on unmount
+ useEffect(() => {
+ return () => {
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
+ };
+ }, []);
+
+ const providerConfig = PROVIDER_CONFIG[voice.provider];
+ const initial = voice.name.charAt(0).toUpperCase();
+
+ return (
+
+
+ {/* Avatar */}
+
+ {initial}
+ {voice.isCloned && (
+
+
+
+ )}
+
+
+ {/* Info */}
+
+
+
+ {voice.name}
+
+
+
+
+ {providerConfig.label}
+
+ {voice.isCloned && (
+
+ Cloned
+
+ )}
+
+
+
+ {/* Play button */}
+
+ {isPlaying ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* Usage count */}
+
+
+ {voice.usageCount.toLocaleString()} videos generated
+
+ {isPlaying && (
+
+ {[...Array(4)].map((_, i) => (
+
+ ))}
+
+ )}
+
+
+ );
+}
+
+// Re-export the Voice type for use in pages
+export type { Voice, VoiceProvider };
diff --git a/frontend/src/hooks/use-keyboard-shortcut.ts b/frontend/src/hooks/use-keyboard-shortcut.ts
new file mode 100644
index 0000000..a61a467
--- /dev/null
+++ b/frontend/src/hooks/use-keyboard-shortcut.ts
@@ -0,0 +1,53 @@
+"use client";
+
+import { useEffect, useCallback } from "react";
+
+type KeyCombo = {
+ key: string;
+ meta?: boolean;
+ ctrl?: boolean;
+ shift?: boolean;
+ alt?: boolean;
+};
+
+export function useKeyboardShortcut(
+ combo: KeyCombo,
+ callback: () => void,
+ enabled: boolean = true
+) {
+ const { key, meta, ctrl, shift, alt } = combo;
+
+ const handleKeyDown = useCallback(
+ (event: KeyboardEvent) => {
+ if (!enabled) return;
+
+ const target = event.target as HTMLElement;
+ const isInput =
+ target.tagName === "INPUT" ||
+ target.tagName === "TEXTAREA" ||
+ target.isContentEditable;
+
+ // Allow meta/ctrl shortcuts even in inputs
+ if (isInput && !meta && !ctrl) return;
+
+ const metaMatch = meta ? event.metaKey : !event.metaKey;
+ const ctrlMatch = ctrl ? event.ctrlKey : !event.ctrlKey;
+ const shiftMatch = shift ? event.shiftKey : true;
+ const altMatch = alt ? event.altKey : !event.altKey;
+ const keyMatch = event.key.toLowerCase() === key.toLowerCase();
+
+ if (keyMatch && metaMatch && ctrlMatch && shiftMatch && altMatch) {
+ event.preventDefault();
+ event.stopPropagation();
+ callback();
+ }
+ },
+ [key, meta, ctrl, shift, alt, callback, enabled]
+ );
+
+ useEffect(() => {
+ if (!enabled) return;
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, [handleKeyDown, enabled]);
+}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
new file mode 100644
index 0000000..cd2d4a8
--- /dev/null
+++ b/frontend/src/lib/api.ts
@@ -0,0 +1,253 @@
+import {
+ clearTokens,
+ getAccessToken,
+ getRefreshToken,
+ isTokenExpired,
+ setTokens,
+} from "@/lib/auth";
+
+const API_BASE = process.env.NEXT_PUBLIC_API_URL || "/api/v1";
+
+class ApiClient {
+ private baseUrl: string;
+ private isRefreshing = false;
+ private refreshPromise: Promise | null = null;
+
+ constructor(baseUrl: string = API_BASE) {
+ this.baseUrl = baseUrl;
+ }
+
+ // --------------------------------------------------------------------------
+ // Token helpers
+ // --------------------------------------------------------------------------
+
+ private getAuthHeaders(): Record {
+ const token = getAccessToken();
+ if (!token) return {};
+ return { Authorization: `Bearer ${token}` };
+ }
+
+ /**
+ * Attempt to refresh the access token using the stored refresh token.
+ * Returns true if successful, false otherwise.
+ * De-duplicated so concurrent 401s don't fire multiple refresh calls.
+ */
+ private async refreshAccessToken(): Promise {
+ if (this.isRefreshing && this.refreshPromise) {
+ return this.refreshPromise;
+ }
+
+ this.isRefreshing = true;
+ this.refreshPromise = (async () => {
+ try {
+ const refreshToken = getRefreshToken();
+ if (!refreshToken) return false;
+
+ const res = await fetch(`${this.baseUrl}/auth/refresh`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ refresh_token: refreshToken }),
+ });
+
+ if (!res.ok) return false;
+
+ const data = await res.json();
+ setTokens(data.access_token, data.refresh_token);
+ return true;
+ } catch {
+ return false;
+ } finally {
+ this.isRefreshing = false;
+ this.refreshPromise = null;
+ }
+ })();
+
+ return this.refreshPromise;
+ }
+
+ // --------------------------------------------------------------------------
+ // Core request
+ // --------------------------------------------------------------------------
+
+ private async request(
+ path: string,
+ options: RequestInit = {},
+ retry = true
+ ): Promise {
+ // If the token is about to expire, proactively refresh
+ if (isTokenExpired() && getRefreshToken()) {
+ await this.refreshAccessToken();
+ }
+
+ const url = `${this.baseUrl}${path}`;
+ const headers: Record = {
+ "Content-Type": "application/json",
+ ...this.getAuthHeaders(),
+ ...(options.headers as Record),
+ };
+
+ const res = await fetch(url, { ...options, headers });
+
+ // Handle 401 — attempt one token refresh, then retry the original request
+ if (res.status === 401 && retry) {
+ const refreshed = await this.refreshAccessToken();
+ if (refreshed) {
+ return this.request(path, options, false);
+ }
+
+ // Refresh failed — clear auth state and redirect to login
+ clearTokens();
+ if (typeof window !== "undefined") {
+ window.location.href = "/landing";
+ }
+ throw new Error("Session expired. Please sign in again.");
+ }
+
+ if (!res.ok) {
+ const error = await res
+ .json()
+ .catch(() => ({ detail: res.statusText }));
+ throw new Error(error.detail || `API error: ${res.status}`);
+ }
+
+ return res.json();
+ }
+
+ // --------------------------------------------------------------------------
+ // Videos
+ // --------------------------------------------------------------------------
+
+ async getVideos(params?: Record) {
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
+ return this.request(`/videos${query}`);
+ }
+
+ async getVideo(id: string) {
+ return this.request(`/videos/${id}`);
+ }
+
+ async createVideo(data: {
+ topic: string;
+ channel_id: string;
+ format?: string;
+ }) {
+ return this.request("/videos", {
+ method: "POST",
+ body: JSON.stringify(data),
+ });
+ }
+
+ async deleteVideo(id: string) {
+ return this.request(`/videos/${id}`, { method: "DELETE" });
+ }
+
+ // --------------------------------------------------------------------------
+ // Channels
+ // --------------------------------------------------------------------------
+
+ async getChannels() {
+ return this.request("/channels");
+ }
+
+ async getChannel(id: string) {
+ return this.request(`/channels/${id}`);
+ }
+
+ async createChannel(data: { name: string; profile_path?: string }) {
+ return this.request("/channels", {
+ method: "POST",
+ body: JSON.stringify(data),
+ });
+ }
+
+ // --------------------------------------------------------------------------
+ // Jobs
+ // --------------------------------------------------------------------------
+
+ async getJobs(params?: Record) {
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
+ return this.request(`/jobs${query}`);
+ }
+
+ async getJob(id: string) {
+ return this.request(`/jobs/${id}`);
+ }
+
+ async getJobProgress(id: string) {
+ return this.request(`/jobs/${id}`);
+ }
+
+ async cancelJob(id: string) {
+ return this.request(`/jobs/${id}/cancel`, { method: "POST" });
+ }
+
+ async retryJob(id: string) {
+ return this.request(`/jobs/${id}/retry`, { method: "POST" });
+ }
+
+ // --------------------------------------------------------------------------
+ // Generate
+ // --------------------------------------------------------------------------
+
+ async generateScript(data: {
+ topic: string;
+ style: string;
+ niche: string;
+ duration: string;
+ }) {
+ return this.request<{
+ title: string;
+ hook: string;
+ sections: { heading: string; content: string }[];
+ outro: string;
+ tags: string[];
+ total_words: number;
+ estimated_duration_seconds: number;
+ }>("/generate/script", {
+ method: "POST",
+ body: JSON.stringify(data),
+ });
+ }
+
+ async generateVideo(data: {
+ topic: string;
+ channel_name: string;
+ style: string;
+ format?: string;
+ voice: string;
+ music_style: string;
+ caption_style: string;
+ duration: string;
+ }) {
+ return this.request<{
+ job_id: string;
+ status: string;
+ }>("/generate/video", {
+ method: "POST",
+ body: JSON.stringify(data),
+ });
+ }
+
+ // --------------------------------------------------------------------------
+ // Analytics
+ // --------------------------------------------------------------------------
+
+ async getCostEstimate(params: { style: string; duration: string }) {
+ const query = "?" + new URLSearchParams(params).toString();
+ return this.request<{
+ estimated_cost: number;
+ breakdown: Record;
+ }>(`/analytics/estimate${query}`);
+ }
+
+ async getCosts(params?: Record) {
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
+ return this.request(`/analytics/costs${query}`);
+ }
+
+ async getVideoAnalytics(videoId: string) {
+ return this.request(`/analytics/video/${videoId}/cost`);
+ }
+}
+
+export const api = new ApiClient();
diff --git a/frontend/src/lib/auth.ts b/frontend/src/lib/auth.ts
new file mode 100644
index 0000000..7a7b503
--- /dev/null
+++ b/frontend/src/lib/auth.ts
@@ -0,0 +1,102 @@
+const ACCESS_TOKEN_KEY = "aividio_access_token";
+const REFRESH_TOKEN_KEY = "aividio_refresh_token";
+
+export interface TokenUser {
+ id: string;
+ email: string;
+ name: string;
+ exp: number;
+}
+
+/**
+ * Decode a JWT payload without a library.
+ * Does NOT verify the signature — that is the server's responsibility.
+ */
+function decodeJwtPayload(token: string): Record | null {
+ try {
+ const base64 = token.split(".")[1];
+ if (!base64) return null;
+ const json = atob(base64.replace(/-/g, "+").replace(/_/g, "/"));
+ return JSON.parse(json);
+ } catch {
+ return null;
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Token storage
+// ---------------------------------------------------------------------------
+
+export function getAccessToken(): string | null {
+ if (typeof window === "undefined") return null;
+ return localStorage.getItem(ACCESS_TOKEN_KEY);
+}
+
+export function getRefreshToken(): string | null {
+ if (typeof window === "undefined") return null;
+ return localStorage.getItem(REFRESH_TOKEN_KEY);
+}
+
+export function setTokens(access: string, refresh?: string): void {
+ if (typeof window === "undefined") return;
+ localStorage.setItem(ACCESS_TOKEN_KEY, access);
+ if (refresh) {
+ localStorage.setItem(REFRESH_TOKEN_KEY, refresh);
+ }
+}
+
+export function clearTokens(): void {
+ if (typeof window === "undefined") return;
+ localStorage.removeItem(ACCESS_TOKEN_KEY);
+ localStorage.removeItem(REFRESH_TOKEN_KEY);
+}
+
+// ---------------------------------------------------------------------------
+// Derived state helpers
+// ---------------------------------------------------------------------------
+
+export function getUserFromToken(): TokenUser | null {
+ const token = getAccessToken();
+ if (!token) return null;
+
+ const payload = decodeJwtPayload(token);
+ if (!payload) return null;
+
+ return {
+ id: (payload.sub as string) || (payload.id as string) || "",
+ email: (payload.email as string) || "",
+ name: (payload.name as string) || "",
+ exp: (payload.exp as number) || 0,
+ };
+}
+
+export function isTokenExpired(token?: string | null): boolean {
+ const t = token ?? getAccessToken();
+ if (!t) return true;
+
+ const payload = decodeJwtPayload(t);
+ if (!payload || typeof payload.exp !== "number") return true;
+
+ // Consider expired 30 seconds before actual expiry to allow buffer
+ return Date.now() >= (payload.exp - 30) * 1000;
+}
+
+export function isAuthenticated(): boolean {
+ const token = getAccessToken();
+ return !!token && !isTokenExpired(token);
+}
+
+/**
+ * Returns the number of milliseconds until the access token expires.
+ * Returns 0 if already expired or no token exists.
+ */
+export function msUntilExpiry(): number {
+ const token = getAccessToken();
+ if (!token) return 0;
+
+ const payload = decodeJwtPayload(token);
+ if (!payload || typeof payload.exp !== "number") return 0;
+
+ const remaining = payload.exp * 1000 - Date.now();
+ return Math.max(0, remaining);
+}
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
new file mode 100644
index 0000000..f06bb8b
--- /dev/null
+++ b/frontend/src/lib/utils.ts
@@ -0,0 +1,43 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+
+export function formatDuration(seconds: number): string {
+ const mins = Math.floor(seconds / 60);
+ const secs = Math.floor(seconds % 60);
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
+}
+
+export function formatDate(date: string | Date): string {
+ return new Date(date).toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ });
+}
+
+export function formatRelativeTime(date: string | Date): string {
+ const now = new Date();
+ const then = new Date(date);
+ const diff = now.getTime() - then.getTime();
+ const seconds = Math.floor(diff / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ if (seconds < 60) return "just now";
+ if (minutes < 60) return `${minutes}m ago`;
+ if (hours < 24) return `${hours}h ago`;
+ if (days < 7) return `${days}d ago`;
+ return formatDate(date);
+}
+
+export function truncate(str: string, length: number): string {
+ if (str.length <= length) return str;
+ return str.slice(0, length) + "...";
+}
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
new file mode 100644
index 0000000..4f2c863
--- /dev/null
+++ b/frontend/src/types/index.ts
@@ -0,0 +1,66 @@
+export type VideoFormat = "landscape" | "portrait" | "short";
+export type VideoStatus = "draft" | "generating" | "ready" | "uploaded" | "failed";
+export type JobStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
+export type JobType = "full_pipeline" | "script_only" | "tts_only" | "video_only" | "upload_only" | "thumbnail_only";
+
+export interface Video {
+ id: string;
+ channel_id: string;
+ title: string;
+ description: string;
+ tags: string[];
+ topic_prompt: string;
+ script_json: Record | null;
+ format: VideoFormat;
+ status: VideoStatus;
+ youtube_video_id: string | null;
+ youtube_url: string | null;
+ duration_seconds: number | null;
+ file_path: string | null;
+ thumbnail_path: string | null;
+ error_message: string | null;
+ created_at: string;
+ channel: Channel;
+ jobs: Job[];
+}
+
+export interface Channel {
+ id: string;
+ name: string;
+ youtube_channel_id: string | null;
+ profile_path: string;
+ is_active: boolean;
+ created_at: string;
+}
+
+export interface Job {
+ id: string;
+ video_id: string;
+ job_type: JobType;
+ status: JobStatus;
+ current_stage: string;
+ progress_pct: number;
+ started_at: string | null;
+ completed_at: string | null;
+ error_detail: string | null;
+ created_at: string;
+ video?: Video;
+}
+
+export interface UsageEvent {
+ id: string;
+ service: string;
+ operation: string;
+ cost_usd: number;
+ tokens_used: number | null;
+ duration_seconds: number | null;
+ model_name: string | null;
+ created_at: string;
+}
+
+export interface CostSummary {
+ total_cost: number;
+ total_calls: number;
+ by_service: Record;
+ daily_trend: { date: string; cost: number }[];
+}
diff --git a/frontend/src/types/wizard.ts b/frontend/src/types/wizard.ts
new file mode 100644
index 0000000..50ddd82
--- /dev/null
+++ b/frontend/src/types/wizard.ts
@@ -0,0 +1,70 @@
+export type VideoStyle = "dark-finance" | "stock-footage" | "ai-cinematic" | "educational";
+export type VideoNiche = "finance" | "tech" | "self-improvement" | "business" | "crypto" | "health";
+export type VideoDuration = "short" | "medium" | "long";
+export type VoiceId = "onyx" | "echo" | "nova" | "alloy";
+export type MusicStyle = "ambient" | "cinematic" | "upbeat" | "dark" | "none";
+export type CaptionStyle = "yellow-keyword" | "white-clean" | "bold-centered";
+export type ColorGrade = "cinematic-warm" | "cool-moody" | "natural" | "custom";
+
+export interface ScriptSection {
+ id: string;
+ heading: string;
+ content: string;
+ wordCount: number;
+}
+
+export interface WizardData {
+ // Step 1: Topic & Style
+ topic: string;
+ style: VideoStyle | null;
+ niche: VideoNiche | null;
+ duration: VideoDuration;
+
+ // Step 2: Script
+ script: ScriptSection[];
+ totalWordCount: number;
+ estimatedDuration: number; // seconds
+
+ // Step 3: Voice & Music
+ voiceId: VoiceId;
+ musicStyle: MusicStyle;
+ musicVolume: number; // 0-100
+
+ // Step 4: Visuals
+ captionStyle: CaptionStyle;
+ colorGrade: ColorGrade;
+ kenBurns: boolean;
+ filmGrain: boolean;
+}
+
+export interface WizardStepProps {
+ data: WizardData;
+ onUpdate: (updates: Partial) => void;
+ onNext: () => void;
+ onBack: () => void;
+}
+
+export type PipelineStage =
+ | "script"
+ | "voiceover"
+ | "images"
+ | "assembly"
+ | "captions"
+ | "export";
+
+export interface PipelineProgress {
+ currentStage: PipelineStage;
+ completedStages: PipelineStage[];
+ progressPct: number;
+ estimatedTimeRemaining: number | null; // seconds
+ videoId: string | null;
+}
+
+export const PIPELINE_STAGES: { id: PipelineStage; label: string }[] = [
+ { id: "script", label: "Script" },
+ { id: "voiceover", label: "Voiceover" },
+ { id: "images", label: "Images" },
+ { id: "assembly", label: "Assembly" },
+ { id: "captions", label: "Captions" },
+ { id: "export", label: "Export" },
+];
diff --git a/frontend/tsconfig 2.json b/frontend/tsconfig 2.json
new file mode 100644
index 0000000..877b650
--- /dev/null
+++ b/frontend/tsconfig 2.json
@@ -0,0 +1,41 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ]
+ },
+ "target": "ES2017"
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/frontend/tsconfig 2.tsbuildinfo b/frontend/tsconfig 2.tsbuildinfo
new file mode 100644
index 0000000..e429ecc
--- /dev/null
+++ b/frontend/tsconfig 2.tsbuildinfo
@@ -0,0 +1 @@
+{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/typescript/lib/lib.es2025.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/typescript/lib/lib.es2025.collection.d.ts","./node_modules/typescript/lib/lib.es2025.float16.d.ts","./node_modules/typescript/lib/lib.es2025.intl.d.ts","./node_modules/typescript/lib/lib.es2025.iterator.d.ts","./node_modules/typescript/lib/lib.es2025.promise.d.ts","./node_modules/typescript/lib/lib.es2025.regexp.d.ts","./node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/typescript/lib/lib.esnext.date.d.ts","./node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.error.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts","./node_modules/typescript/lib/lib.esnext.temporal.d.ts","./node_modules/typescript/lib/lib.esnext.typedarrays.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/next/dist/server/get-page-files.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/blob.d.ts","./node_modules/@types/node/web-globals/console.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/encoding.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/round-robin-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/importmeta.d.ts","./node_modules/@types/node/web-globals/messaging.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/performance.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/web-globals/timers.d.ts","./node_modules/@types/node/web-globals/url.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/inspector/promises.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/path/posix.d.ts","./node_modules/@types/node/path/win32.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/quic.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/test/reporters.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/util/types.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/@types/react/canary.d.ts","./node_modules/@types/react/experimental.d.ts","./node_modules/@types/react-dom/index.d.ts","./node_modules/@types/react-dom/canary.d.ts","./node_modules/@types/react-dom/experimental.d.ts","./node_modules/next/dist/lib/fallback.d.ts","./node_modules/next/dist/compiled/webpack/webpack.d.ts","./node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","./node_modules/next/dist/shared/lib/entry-constants.d.ts","./node_modules/next/dist/shared/lib/constants.d.ts","./node_modules/next/dist/lib/bundler.d.ts","./node_modules/next/dist/server/config.d.ts","./node_modules/next/dist/lib/load-custom-routes.d.ts","./node_modules/next/dist/shared/lib/image-config.d.ts","./node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","./node_modules/next/dist/server/body-streams.d.ts","./node_modules/next/dist/server/request/search-params.d.ts","./node_modules/next/dist/shared/lib/segment-cache/vary-params-decoding.d.ts","./node_modules/next/dist/server/app-render/vary-params.d.ts","./node_modules/next/dist/server/request/params.d.ts","./node_modules/next/dist/server/route-kind.d.ts","./node_modules/next/dist/server/route-definitions/route-definition.d.ts","./node_modules/next/dist/server/route-matches/route-match.d.ts","./node_modules/next/dist/client/components/app-router-headers.d.ts","./node_modules/next/dist/server/lib/cache-control.d.ts","./node_modules/next/dist/shared/lib/app-router-types.d.ts","./node_modules/next/dist/server/lib/cache-handlers/types.d.ts","./node_modules/next/dist/server/use-cache/use-cache-wrapper.d.ts","./node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","./node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","./node_modules/next/dist/lib/constants.d.ts","./node_modules/next/dist/server/render-result.d.ts","./node_modules/next/dist/server/response-cache/types.d.ts","./node_modules/next/dist/server/response-cache/index.d.ts","./node_modules/@types/react/jsx-runtime.d.ts","./node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","./node_modules/next/dist/build/static-paths/types.d.ts","./node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","./node_modules/next/dist/build/adapter/setup-node-env.external.d.ts","./node_modules/next/dist/server/instrumentation/types.d.ts","./node_modules/next/dist/lib/setup-exception-listeners.d.ts","./node_modules/next/dist/lib/worker.d.ts","./node_modules/next/dist/server/lib/experimental/ppr.d.ts","./node_modules/next/dist/lib/page-types.d.ts","./node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","./node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","./node_modules/next/dist/build/analysis/get-page-static-info.d.ts","./node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","./node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","./node_modules/next/dist/server/require-hook.d.ts","./node_modules/next/dist/server/node-polyfill-crypto.d.ts","./node_modules/next/dist/server/node-environment-baseline.d.ts","./node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-file.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts","./node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.external.d.ts","./node_modules/next/dist/server/node-environment-extensions/random.d.ts","./node_modules/next/dist/server/node-environment-extensions/date.d.ts","./node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","./node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","./node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts","./node_modules/next/dist/server/node-environment.d.ts","./node_modules/next/dist/build/page-extensions-type.d.ts","./node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","./node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","./node_modules/next/dist/server/lib/i18n-provider.d.ts","./node_modules/next/dist/server/web/next-url.d.ts","./node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","./node_modules/next/dist/server/web/spec-extension/cookies.d.ts","./node_modules/next/dist/server/web/spec-extension/request.d.ts","./node_modules/next/dist/shared/lib/deep-readonly.d.ts","./node_modules/next/dist/server/lib/incremental-cache/index.d.ts","./node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","./node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","./node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","./node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","./node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","./node_modules/next/dist/shared/lib/mitt.d.ts","./node_modules/next/dist/client/with-router.d.ts","./node_modules/next/dist/client/router.d.ts","./node_modules/next/dist/client/route-loader.d.ts","./node_modules/next/dist/client/page-loader.d.ts","./node_modules/next/dist/shared/lib/bloom-filter.d.ts","./node_modules/next/dist/shared/lib/router/router.d.ts","./node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","./node_modules/next/dist/client/components/readonly-url-search-params.d.ts","./node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","./node_modules/next/dist/client/flight-data-helpers.d.ts","./node_modules/next/dist/client/components/segment-cache/cache-key.d.ts","./node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","./node_modules/next/dist/client/components/segment-cache/types.d.ts","./node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","./node_modules/next/dist/client/components/segment-cache/scheduler.d.ts","./node_modules/next/dist/client/components/segment-cache/cache-map.d.ts","./node_modules/next/dist/client/components/segment-cache/vary-path.d.ts","./node_modules/next/dist/client/components/segment-cache/cache.d.ts","./node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts","./node_modules/next/dist/client/components/segment-cache/navigation.d.ts","./node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","./node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","./node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","./node_modules/next/dist/build/templates/pages.d.ts","./node_modules/next/dist/server/route-modules/pages/module.d.ts","./node_modules/next/dist/server/render.d.ts","./node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","./node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","./node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","./node_modules/next/dist/server/route-matchers/route-matcher.d.ts","./node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","./node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","./node_modules/next/dist/server/normalizers/normalizer.d.ts","./node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","./node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","./node_modules/next/dist/server/normalizers/request/suffix.d.ts","./node_modules/next/dist/server/normalizers/request/rsc.d.ts","./node_modules/next/dist/server/normalizers/request/next-data.d.ts","./node_modules/next/dist/server/after/builtin-request-context.d.ts","./node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","./node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts","./node_modules/next/dist/server/load-default-error-components.d.ts","./node_modules/next/dist/server/base-server.d.ts","./node_modules/next/dist/server/after/after.d.ts","./node_modules/next/dist/server/after/after-context.d.ts","./node_modules/next/dist/server/use-cache/cache-life.d.ts","./node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","./node_modules/next/dist/server/lib/lazy-result.d.ts","./node_modules/next/dist/server/app-render/create-error-handler.d.ts","./node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts","./node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","./node_modules/next/dist/server/async-storage/work-store.d.ts","./node_modules/next/dist/server/web/http.d.ts","./node_modules/next/dist/client/components/hooks-server-context.d.ts","./node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","./node_modules/next/dist/client/components/redirect-status-code.d.ts","./node_modules/next/dist/client/components/redirect-error.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","./node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","./node_modules/next/dist/server/app-render/cache-signal.d.ts","./node_modules/next/dist/server/app-render/instant-validation/boundary-tracking.d.ts","./node_modules/next/dist/server/app-render/instant-validation/instant-validation-error.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","./node_modules/next/dist/server/app-render/instant-validation/instant-samples.d.ts","./node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","./node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","./node_modules/next/dist/server/lib/implicit-tags.d.ts","./node_modules/next/dist/server/app-render/staged-rendering.d.ts","./node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","./node_modules/next/dist/build/templates/app-route.d.ts","./node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","./node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","./node_modules/next/dist/server/route-modules/app-route/module.d.ts","./node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","./node_modules/next/dist/build/segment-config/app/app-segments.d.ts","./node_modules/next/dist/build/get-supported-browsers.d.ts","./node_modules/next/dist/build/utils.d.ts","./node_modules/next/dist/build/rendering-mode.d.ts","./node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","./node_modules/next/dist/server/lib/cpu-profile.d.ts","./node_modules/next/dist/build/turborepo-access-trace/types.d.ts","./node_modules/next/dist/build/turborepo-access-trace/result.d.ts","./node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","./node_modules/next/dist/build/turborepo-access-trace/index.d.ts","./node_modules/next/dist/export/routes/types.d.ts","./node_modules/next/dist/export/types.d.ts","./node_modules/next/dist/export/worker.d.ts","./node_modules/next/dist/build/worker.d.ts","./node_modules/next/dist/build/index.d.ts","./node_modules/next/dist/lib/coalesced-function.d.ts","./node_modules/next/dist/server/lib/router-utils/types.d.ts","./node_modules/next/dist/trace/types.d.ts","./node_modules/next/dist/trace/trace.d.ts","./node_modules/next/dist/trace/shared.d.ts","./node_modules/next/dist/trace/index.d.ts","./node_modules/next/dist/build/load-jsconfig.d.ts","./node_modules/@next/env/dist/index.d.ts","./node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","./node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","./node_modules/next/dist/telemetry/storage.d.ts","./node_modules/next/dist/build/build-context.d.ts","./node_modules/next/dist/build/webpack-config.d.ts","./node_modules/next/dist/build/swc/generated-native.d.ts","./node_modules/next/dist/build/define-env.d.ts","./node_modules/next/dist/build/swc/index.d.ts","./node_modules/next/dist/build/swc/types.d.ts","./node_modules/next/dist/server/dev/parse-version-info.d.ts","./node_modules/next/dist/next-devtools/shared/types.d.ts","./node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts","./node_modules/next/dist/server/lib/parse-stack.d.ts","./node_modules/next/dist/next-devtools/server/shared.d.ts","./node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","./node_modules/next/dist/server/dev/debug-channel.d.ts","./node_modules/next/dist/server/dev/hot-reloader-types.d.ts","./node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","./node_modules/next/dist/server/web/spec-extension/response.d.ts","./node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","./node_modules/next/dist/server/web/types.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","./node_modules/next/dist/server/base-http/node.d.ts","./node_modules/next/dist/server/lib/async-callback-set.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","./node_modules/sharp/lib/index.d.ts","./node_modules/next/dist/server/image-optimizer.d.ts","./node_modules/next/dist/server/next-server.d.ts","./node_modules/next/dist/server/lib/types.d.ts","./node_modules/next/dist/server/lib/lru-cache.d.ts","./node_modules/next/dist/server/lib/dev-bundler-service.d.ts","./node_modules/next/dist/server/dev/static-paths-worker.d.ts","./node_modules/next/dist/server/dev/next-dev-server.d.ts","./node_modules/next/dist/server/next.d.ts","./node_modules/next/dist/server/lib/render-server.d.ts","./node_modules/next/dist/server/lib/router-server.d.ts","./node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","./node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","./node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","./node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","./node_modules/next/dist/server/route-modules/route-module.d.ts","./node_modules/next/dist/server/load-components.d.ts","./node_modules/next/dist/server/web/adapter.d.ts","./node_modules/next/dist/server/app-render/types.d.ts","./node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","./node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","./node_modules/next/dist/server/lib/app-dir-module.d.ts","./node_modules/next/dist/server/app-render/app-render.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/client/components/error-boundary.d.ts","./node_modules/next/dist/client/components/layout-router.d.ts","./node_modules/next/dist/client/components/render-from-template-context.d.ts","./node_modules/next/dist/client/components/client-page.d.ts","./node_modules/next/dist/client/components/client-segment.d.ts","./node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","./node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","./node_modules/next/dist/lib/metadata/types/extra-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","./node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","./node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","./node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","./node_modules/next/dist/lib/metadata/types/resolvers.d.ts","./node_modules/next/dist/lib/metadata/types/icons.d.ts","./node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","./node_modules/next/dist/lib/metadata/metadata.d.ts","./node_modules/next/dist/lib/framework/boundary-components.d.ts","./node_modules/next/dist/server/app-render/rsc/preloads.d.ts","./node_modules/next/dist/server/app-render/rsc/postpone.d.ts","./node_modules/next/dist/server/app-render/rsc/taint.d.ts","./node_modules/next/dist/server/app-render/collect-segment-data.d.ts","./node_modules/next/dist/server/app-render/instant-validation/instant-validation.d.ts","./node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","./node_modules/next/dist/server/app-render/entry-base.d.ts","./node_modules/next/dist/build/templates/app-page.d.ts","./node_modules/next/dist/server/route-modules/app-page/helpers/prerender-manifest-matcher.d.ts","./node_modules/@types/react/jsx-dev-runtime.d.ts","./node_modules/@types/react/compiler-runtime.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","./node_modules/@types/react-dom/client.d.ts","./node_modules/@types/react-dom/static.d.ts","./node_modules/@types/react-dom/server.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/app-page/module.d.ts","./node_modules/next/dist/server/request/fallback-params.d.ts","./node_modules/next/dist/server/web/spec-extension/image-response.d.ts","./node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","./node_modules/next/dist/server/web/spec-extension/url-pattern.d.ts","./node_modules/next/dist/server/after/index.d.ts","./node_modules/next/dist/server/request/connection.d.ts","./node_modules/next/dist/server/web/exports/index.d.ts","./node_modules/next/dist/server/request-meta.d.ts","./node_modules/next/dist/cli/next-test.d.ts","./node_modules/next/dist/shared/lib/size-limit.d.ts","./node_modules/next/dist/server/config-shared.d.ts","./node_modules/next/dist/server/base-http/index.d.ts","./node_modules/next/dist/server/api-utils/index.d.ts","./node_modules/next/dist/build/adapter/build-complete.d.ts","./node_modules/next/dist/types.d.ts","./node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/utils.d.ts","./node_modules/next/dist/pages/_app.d.ts","./node_modules/next/app.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","./node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","./node_modules/next/dist/server/use-cache/cache-tag.d.ts","./node_modules/next/cache.d.ts","./node_modules/next/dist/pages/_document.d.ts","./node_modules/next/document.d.ts","./node_modules/next/dist/shared/lib/dynamic.d.ts","./node_modules/next/dynamic.d.ts","./node_modules/next/dist/pages/_error.d.ts","./node_modules/next/dist/client/components/catch-error.d.ts","./node_modules/next/dist/api/error.d.ts","./node_modules/next/error.d.ts","./node_modules/next/dist/shared/lib/head.d.ts","./node_modules/next/head.d.ts","./node_modules/next/dist/server/request/cookies.d.ts","./node_modules/next/dist/server/request/headers.d.ts","./node_modules/next/dist/server/request/draft-mode.d.ts","./node_modules/next/headers.d.ts","./node_modules/next/dist/shared/lib/get-img-props.d.ts","./node_modules/next/dist/client/image-component.d.ts","./node_modules/next/dist/shared/lib/image-external.d.ts","./node_modules/next/image.d.ts","./node_modules/next/dist/client/link.d.ts","./node_modules/next/link.d.ts","./node_modules/next/dist/client/components/unrecognized-action-error.d.ts","./node_modules/next/dist/client/components/redirect.d.ts","./node_modules/next/dist/client/components/not-found.d.ts","./node_modules/next/dist/client/components/forbidden.d.ts","./node_modules/next/dist/client/components/unauthorized.d.ts","./node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","./node_modules/next/dist/client/components/unstable-rethrow.d.ts","./node_modules/next/dist/client/components/navigation.react-server.d.ts","./node_modules/next/dist/client/components/navigation.d.ts","./node_modules/next/navigation.d.ts","./node_modules/next/router.d.ts","./node_modules/next/dist/client/script.d.ts","./node_modules/next/script.d.ts","./node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","./node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/types.d.ts","./node_modules/next/server.d.ts","./node_modules/next/types/global.d.ts","./node_modules/next/types/compiled.d.ts","./node_modules/next/types.d.ts","./node_modules/next/index.d.ts","./node_modules/next/image-types/global.d.ts","./.next/types/routes.d.ts","./next-env.d.ts","./next.config.ts","./src/hooks/use-keyboard-shortcut.ts","./src/lib/auth.ts","./src/lib/api.ts","./node_modules/clsx/clsx.d.mts","./node_modules/tailwind-merge/dist/types.d.ts","./src/lib/utils.ts","./src/types/index.ts","./src/types/wizard.ts","./node_modules/next/dist/compiled/@next/font/dist/types.d.ts","./node_modules/next/dist/compiled/@next/font/dist/google/index.d.ts","./node_modules/next/font/google/index.d.ts","./src/components/auth/auth-provider.tsx","./src/components/auth/auth-guard.tsx","./node_modules/@radix-ui/react-slot/dist/index.d.mts","./node_modules/class-variance-authority/dist/types.d.ts","./node_modules/class-variance-authority/dist/index.d.ts","./src/components/ui/button.tsx","./node_modules/@radix-ui/react-context/dist/index.d.mts","./node_modules/@radix-ui/react-primitive/dist/index.d.mts","./node_modules/@radix-ui/react-dismissable-layer/dist/index.d.mts","./node_modules/@radix-ui/react-arrow/dist/index.d.mts","./node_modules/@radix-ui/rect/dist/index.d.mts","./node_modules/@radix-ui/react-popper/dist/index.d.mts","./node_modules/@radix-ui/react-portal/dist/index.d.mts","./node_modules/@radix-ui/react-tooltip/dist/index.d.mts","./src/components/ui/tooltip.tsx","./node_modules/lucide-react/dist/lucide-react.d.ts","./src/components/layout/sidebar.tsx","./src/components/layout/header.tsx","./node_modules/@radix-ui/react-focus-scope/dist/index.d.mts","./node_modules/@radix-ui/react-dialog/dist/index.d.mts","./src/components/layout/command-palette.tsx","./src/components/layout/app-shell.tsx","./src/components/layout/layout-router.tsx","./src/app/layout.tsx","./src/app/not-found.tsx","./src/components/shared/status-dot.tsx","./node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context/dist/index.d.mts","./node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive/dist/index.d.mts","./node_modules/@radix-ui/react-progress/dist/index.d.mts","./src/components/ui/progress.tsx","./src/components/dashboard/active-jobs.tsx","./src/components/dashboard/cost-widget.tsx","./src/components/ui/table.tsx","./src/components/videos/video-table.tsx","./src/components/dashboard/recent-videos.tsx","./src/app/(dashboard)/page.tsx","./src/components/ui/card.tsx","./src/components/shared/page-header.tsx","./node_modules/@types/d3-time/index.d.ts","./node_modules/@types/d3-scale/index.d.ts","./node_modules/victory-vendor/d3-scale.d.ts","./node_modules/recharts/types/shape/dot.d.ts","./node_modules/recharts/types/component/text.d.ts","./node_modules/recharts/types/zindex/zindexlayer.d.ts","./node_modules/recharts/types/cartesian/getcartesianposition.d.ts","./node_modules/recharts/types/component/label.d.ts","./node_modules/recharts/types/cartesian/cartesianaxis.d.ts","./node_modules/recharts/types/util/scale/customscaledefinition.d.ts","./node_modules/redux/dist/redux.d.ts","./node_modules/@reduxjs/toolkit/node_modules/immer/dist/immer.d.ts","./node_modules/reselect/dist/reselect.d.ts","./node_modules/redux-thunk/dist/redux-thunk.d.ts","./node_modules/@reduxjs/toolkit/dist/uncheckedindexed.ts","./node_modules/@reduxjs/toolkit/dist/index.d.mts","./node_modules/recharts/types/state/cartesianaxisslice.d.ts","./node_modules/recharts/types/synchronisation/types.d.ts","./node_modules/recharts/types/chart/types.d.ts","./node_modules/recharts/types/component/defaulttooltipcontent.d.ts","./node_modules/recharts/types/context/brushupdatecontext.d.ts","./node_modules/recharts/types/state/chartdataslice.d.ts","./node_modules/recharts/types/state/types/linesettings.d.ts","./node_modules/recharts/types/state/types/scattersettings.d.ts","./node_modules/@types/d3-path/index.d.ts","./node_modules/@types/d3-shape/index.d.ts","./node_modules/victory-vendor/d3-shape.d.ts","./node_modules/recharts/types/shape/curve.d.ts","./node_modules/recharts/types/component/labellist.d.ts","./node_modules/recharts/types/component/defaultlegendcontent.d.ts","./node_modules/recharts/types/util/payload/getuniqpayload.d.ts","./node_modules/recharts/types/util/useelementoffset.d.ts","./node_modules/recharts/types/component/legend.d.ts","./node_modules/recharts/types/state/legendslice.d.ts","./node_modules/recharts/types/state/types/stackedgraphicalitem.d.ts","./node_modules/recharts/types/util/stacks/stacktypes.d.ts","./node_modules/recharts/types/util/scale/rechartsscale.d.ts","./node_modules/recharts/types/util/chartutils.d.ts","./node_modules/recharts/types/state/selectors/areaselectors.d.ts","./node_modules/recharts/types/cartesian/area.d.ts","./node_modules/recharts/types/state/types/areasettings.d.ts","./node_modules/recharts/types/animation/easing.d.ts","./node_modules/recharts/types/shape/rectangle.d.ts","./node_modules/recharts/types/cartesian/bar.d.ts","./node_modules/recharts/types/util/barutils.d.ts","./node_modules/recharts/types/state/types/barsettings.d.ts","./node_modules/recharts/types/state/types/radialbarsettings.d.ts","./node_modules/recharts/types/util/svgpropertiesnoevents.d.ts","./node_modules/recharts/types/util/useuniqueid.d.ts","./node_modules/recharts/types/state/types/piesettings.d.ts","./node_modules/recharts/types/state/types/radarsettings.d.ts","./node_modules/recharts/types/state/graphicalitemsslice.d.ts","./node_modules/recharts/types/state/tooltipslice.d.ts","./node_modules/recharts/types/state/optionsslice.d.ts","./node_modules/recharts/types/state/layoutslice.d.ts","./node_modules/immer/dist/immer.d.ts","./node_modules/recharts/types/util/ifoverflow.d.ts","./node_modules/recharts/types/util/resolvedefaultprops.d.ts","./node_modules/recharts/types/cartesian/referenceline.d.ts","./node_modules/recharts/types/state/referenceelementsslice.d.ts","./node_modules/recharts/types/state/brushslice.d.ts","./node_modules/recharts/types/state/rootpropsslice.d.ts","./node_modules/recharts/types/state/polaraxisslice.d.ts","./node_modules/recharts/types/state/polaroptionsslice.d.ts","./node_modules/recharts/types/cartesian/line.d.ts","./node_modules/recharts/types/util/constants.d.ts","./node_modules/recharts/types/util/scatterutils.d.ts","./node_modules/recharts/types/shape/symbols.d.ts","./node_modules/recharts/types/cartesian/scatter.d.ts","./node_modules/recharts/types/cartesian/errorbar.d.ts","./node_modules/recharts/types/state/errorbarslice.d.ts","./node_modules/recharts/types/state/zindexslice.d.ts","./node_modules/recharts/types/state/eventsettingsslice.d.ts","./node_modules/recharts/types/state/renderedticksslice.d.ts","./node_modules/recharts/types/state/store.d.ts","./node_modules/recharts/types/cartesian/getticks.d.ts","./node_modules/recharts/types/cartesian/cartesiangrid.d.ts","./node_modules/recharts/types/state/selectors/combiners/combinedisplayedstackeddata.d.ts","./node_modules/recharts/types/state/selectors/selecttooltipaxistype.d.ts","./node_modules/recharts/types/types.d.ts","./node_modules/recharts/types/hooks.d.ts","./node_modules/recharts/types/state/selectors/axisselectors.d.ts","./node_modules/recharts/types/component/dots.d.ts","./node_modules/recharts/types/util/typeddatakey.d.ts","./node_modules/recharts/types/util/types.d.ts","./node_modules/recharts/types/container/surface.d.ts","./node_modules/recharts/types/container/layer.d.ts","./node_modules/recharts/types/component/cursor.d.ts","./node_modules/recharts/types/component/tooltip.d.ts","./node_modules/recharts/types/component/responsivecontainer.d.ts","./node_modules/recharts/types/component/cell.d.ts","./node_modules/recharts/types/component/customized.d.ts","./node_modules/recharts/types/shape/sector.d.ts","./node_modules/recharts/types/shape/polygon.d.ts","./node_modules/recharts/types/shape/cross.d.ts","./node_modules/recharts/types/polar/polargrid.d.ts","./node_modules/recharts/types/polar/defaultpolarradiusaxisprops.d.ts","./node_modules/recharts/types/polar/polarradiusaxis.d.ts","./node_modules/recharts/types/polar/defaultpolarangleaxisprops.d.ts","./node_modules/recharts/types/polar/polarangleaxis.d.ts","./node_modules/recharts/types/context/tooltipcontext.d.ts","./node_modules/recharts/types/polar/pie.d.ts","./node_modules/recharts/types/polar/radar.d.ts","./node_modules/recharts/types/util/radialbarutils.d.ts","./node_modules/recharts/types/polar/radialbar.d.ts","./node_modules/recharts/types/cartesian/brush.d.ts","./node_modules/recharts/types/cartesian/referencedot.d.ts","./node_modules/recharts/types/util/excludeeventprops.d.ts","./node_modules/recharts/types/util/svgpropertiesandevents.d.ts","./node_modules/recharts/types/cartesian/referencearea.d.ts","./node_modules/recharts/types/cartesian/barstack.d.ts","./node_modules/recharts/types/cartesian/xaxis.d.ts","./node_modules/recharts/types/cartesian/yaxis.d.ts","./node_modules/recharts/types/cartesian/zaxis.d.ts","./node_modules/recharts/types/chart/linechart.d.ts","./node_modules/recharts/types/chart/barchart.d.ts","./node_modules/recharts/types/chart/piechart.d.ts","./node_modules/recharts/types/chart/treemap.d.ts","./node_modules/recharts/types/chart/sankey.d.ts","./node_modules/recharts/types/chart/radarchart.d.ts","./node_modules/recharts/types/chart/scatterchart.d.ts","./node_modules/recharts/types/chart/areachart.d.ts","./node_modules/recharts/types/chart/radialbarchart.d.ts","./node_modules/recharts/types/chart/composedchart.d.ts","./node_modules/recharts/types/chart/sunburstchart.d.ts","./node_modules/recharts/types/shape/trapezoid.d.ts","./node_modules/recharts/types/cartesian/funnel.d.ts","./node_modules/recharts/types/chart/funnelchart.d.ts","./node_modules/recharts/types/util/global.d.ts","./node_modules/recharts/types/zindex/defaultzindexes.d.ts","./node_modules/decimal.js-light/decimal.d.ts","./node_modules/recharts/types/util/scale/getnicetickvalues.d.ts","./node_modules/recharts/types/context/chartlayoutcontext.d.ts","./node_modules/recharts/types/util/getrelativecoordinate.d.ts","./node_modules/recharts/types/util/createcartesiancharts.d.ts","./node_modules/recharts/types/util/createpolarcharts.d.ts","./node_modules/recharts/types/index.d.ts","./src/components/analytics/cost-chart.tsx","./src/app/analytics/page.tsx","./src/components/shared/empty-state.tsx","./src/components/ui/badge.tsx","./src/components/channels/channel-card.tsx","./src/app/channels/page.tsx","./src/app/channels/[id]/page.tsx","./src/components/ui/input.tsx","./src/app/channels/new/page.tsx","./src/components/content/calendar-grid.tsx","./src/components/content/trending-topics.tsx","./src/app/content/page.tsx","./src/app/forgot-password/page.tsx","./src/components/jobs/job-card.tsx","./src/app/jobs/page.tsx","./src/app/jobs/[id]/page.tsx","./src/app/landing/components/landing-shell.tsx","./src/app/landing/layout.tsx","./src/app/landing/components/hero-visual.tsx","./src/app/landing/components/bento-grid.tsx","./src/app/landing/components/style-showcase.tsx","./src/app/landing/components/pricing-card.tsx","./src/app/landing/page.tsx","./src/app/login/page.tsx","./src/components/schedule/upcoming-list.tsx","./src/app/schedule/page.tsx","./src/app/settings/page.tsx","./src/app/signup/page.tsx","./src/components/templates/template-card.tsx","./src/app/templates/page.tsx","./src/components/videos/video-list-content.tsx","./src/app/videos/page.tsx","./src/components/editor/timeline-ruler.tsx","./src/components/editor/timeline-track.tsx","./src/components/editor/video-timeline.tsx","./src/app/videos/[id]/timeline-wrapper.tsx","./src/app/videos/[id]/page.tsx","./src/components/videos/wizard/wizard-progress.tsx","./src/components/ui/textarea.tsx","./node_modules/@radix-ui/react-select/dist/index.d.mts","./src/components/ui/select.tsx","./src/components/videos/wizard/style-card.tsx","./src/components/videos/wizard/step-topic.tsx","./src/components/videos/wizard/step-script.tsx","./src/components/videos/wizard/voice-preview.tsx","./src/components/videos/wizard/step-voice.tsx","./src/components/videos/wizard/step-visuals.tsx","./src/components/videos/wizard/step-review.tsx","./src/components/videos/generation-progress.tsx","./src/app/videos/new/page.tsx","./src/components/voices/voice-card.tsx","./src/app/voices/page.tsx","./src/app/voices/clone/page.tsx","./src/components/brand/logo.tsx","./src/components/shared/stat-card.tsx","./src/components/ui/dialog.tsx","./node_modules/@radix-ui/react-roving-focus/dist/index.d.mts","./node_modules/@radix-ui/react-tabs/dist/index.d.mts","./src/components/ui/tabs.tsx","./src/components/videos/create-form.tsx","./.next/types/cache-life.d.ts","./.next/types/validator.ts","./.next/dev/types/cache-life.d.ts","./.next/dev/types/routes.d.ts","./.next/dev/types/validator.ts"],"fileIdsList":[[101,164,172,176,179,181,182,183,195,512,513,514,515,808],[101,164,172,176,179,181,182,183,195,808,810],[101,164,172,176,179,181,182,183,195,255,556,596,608,749,753,754,756,759,762,763,773,774,779,784,797,799,800,808,810,811],[101,164,172,176,179,181,182,183,195,512,513,514,515,810],[101,164,172,176,179,181,182,183,195,255,556,559,596,608,749,753,754,756,759,760,762,763,765,770,771,773,774,775,777,779,784,797,799,800,808,810],[101,164,172,176,179,181,182,183,195,557,558,559,808,810],[101,164,172,176,179,181,182,183,195,255,557,808,810],[92,101,164,172,176,179,181,182,183,195,580,808,810],[92,101,164,172,176,179,181,182,183,195,808,810],[92,101,164,172,176,179,181,182,183,195,579,580,581,585,591,808,810],[92,101,164,172,176,179,181,182,183,195,579,580,582,583,808,810],[92,101,164,172,176,179,181,182,183,195,599,600,808,810],[92,101,164,172,176,179,181,182,183,195,579,580,808,810],[92,101,164,172,176,179,181,182,183,195,579,580,581,584,585,591,808,810],[92,96,101,164,172,176,179,181,182,183,195,221,222,223,224,225,507,552,808,810],[92,101,164,172,176,179,181,182,183,195,579,580,804,808,810],[92,101,164,172,176,179,181,182,183,195,579,580,581,584,585,808,810],[101,164,172,176,179,181,182,183,195,621,622,623,624,625,808,810],[101,164,172,176,179,181,182,183,195,255,808,810],[101,164,172,176,179,181,182,183,195,611,808,810],[101,164,172,176,179,181,182,183,195,635,808,810],[101,161,162,164,172,176,179,181,182,183,195,808,810],[101,163,164,172,176,179,181,182,183,195,808,810],[164,172,176,179,181,182,183,195,808,810],[101,164,172,176,179,181,182,183,195,203,808,810],[101,164,165,170,172,175,176,179,181,182,183,185,195,200,212,808,810],[101,164,165,166,172,175,176,179,181,182,183,195,808,810],[101,164,167,172,176,179,181,182,183,195,213,808,810],[101,164,168,169,172,176,179,181,182,183,186,195,808,810],[101,164,169,172,176,179,181,182,183,195,200,209,808,810],[101,164,170,172,175,176,179,181,182,183,185,195,808,810],[101,163,164,171,172,176,179,181,182,183,195,808,810],[101,164,172,173,176,179,181,182,183,195,808,810],[101,164,172,174,175,176,179,181,182,183,195,808,810],[101,163,164,172,175,176,179,181,182,183,195,808,810],[101,164,172,175,176,177,179,181,182,183,195,200,212,808,810],[101,164,172,175,176,177,179,181,182,183,195,200,203,808,810],[101,151,164,172,175,176,178,179,181,182,183,185,195,200,212,808,810],[101,164,172,175,176,178,179,181,182,183,185,195,200,209,212,808,810],[101,164,172,176,178,179,180,181,182,183,195,200,209,212,808,810],[99,100,101,102,103,104,105,106,107,108,109,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,808,810],[101,164,172,175,176,179,181,182,183,195,808,810],[101,164,172,176,179,181,183,195,808,810],[101,164,172,176,179,181,182,183,184,195,212,808,810],[101,164,172,175,176,179,181,182,183,185,195,200,808,810],[101,164,172,176,179,181,182,183,186,195,808,810],[101,164,172,176,179,181,182,183,187,195,808,810],[101,164,172,175,176,179,181,182,183,190,195,808,810],[101,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,808,810],[101,164,172,176,179,181,182,183,192,195,808,810],[101,164,172,176,179,181,182,183,193,195,808,810],[101,164,169,172,176,179,181,182,183,185,195,203,808,810],[101,164,172,175,176,179,181,182,183,195,196,808,810],[101,164,172,176,179,181,182,183,195,197,213,216,808,810],[101,164,172,175,176,179,181,182,183,195,200,202,203,808,810],[101,164,172,176,179,181,182,183,195,201,203,808,810],[101,164,172,176,179,181,182,183,195,203,213,808,810],[101,164,172,176,179,181,182,183,195,204,808,810],[101,161,164,172,176,179,181,182,183,195,200,206,212,808,810],[101,164,172,176,179,181,182,183,195,200,205,808,810],[101,164,172,175,176,179,181,182,183,195,207,208,808,810],[101,164,172,176,179,181,182,183,195,207,208,808,810],[101,164,169,172,176,179,181,182,183,185,195,200,209,808,810],[101,164,172,176,179,181,182,183,195,210,808,810],[101,164,172,176,179,181,182,183,185,195,211,808,810],[101,164,172,176,178,179,181,182,183,193,195,212,808,810],[101,164,172,176,179,181,182,183,195,213,214,808,810],[101,164,169,172,176,179,181,182,183,195,214,808,810],[101,164,172,176,179,181,182,183,195,200,215,808,810],[101,164,172,176,179,181,182,183,184,195,216,808,810],[101,164,172,176,179,181,182,183,195,217,808,810],[101,164,167,172,176,179,181,182,183,195,808,810],[101,164,169,172,176,179,181,182,183,195,808,810],[101,164,172,176,179,181,182,183,195,213,808,810],[101,151,164,172,176,179,181,182,183,195,808,810],[101,164,172,176,179,181,182,183,195,212,808,810],[101,164,172,176,179,181,182,183,195,218,808,810],[101,164,172,176,179,181,182,183,190,195,808,810],[101,164,172,176,179,181,182,183,195,208,808,810],[101,151,164,172,175,176,177,179,181,182,183,190,195,200,203,212,215,216,218,808,810],[101,164,172,176,179,181,182,183,195,200,219,808,810],[92,96,101,164,172,176,179,181,182,183,195,221,222,223,225,507,552,575,808,810],[92,96,101,164,172,176,179,181,182,183,195,221,222,223,224,488,507,552,575,808,810],[92,96,101,164,172,176,179,181,182,183,195,221,222,224,225,507,552,575,808,810],[92,101,164,172,176,179,181,182,183,195,225,488,489,808,810],[92,101,164,172,176,179,181,182,183,195,225,488,808,810],[92,96,101,164,172,176,179,181,182,183,195,222,223,224,225,507,552,575,808,810],[92,96,101,164,172,176,179,181,182,183,195,221,223,224,225,507,552,575,808,810],[90,91,101,164,172,176,179,181,182,183,195,808,810],[101,164,172,176,179,181,182,183,195,565,576,808,810],[101,164,172,176,179,181,182,183,195,565,808,810],[101,164,172,176,179,181,182,183,195,510,808,810],[101,164,172,176,179,181,182,183,195,512,513,514,515,808,810],[101,164,172,176,179,181,182,183,195,458,521,522,808,810],[101,164,172,176,179,181,182,183,195,230,231,233,245,269,384,395,503,808,810],[101,164,172,176,179,181,182,183,195,233,264,265,266,268,503,808,810],[101,164,172,176,179,181,182,183,195,233,401,403,405,406,408,503,505,808,810],[101,164,172,176,179,181,182,183,195,233,267,304,503,808,810],[101,164,172,176,179,181,182,183,195,231,233,244,245,251,257,262,383,384,385,394,503,505,808,810],[101,164,172,176,179,181,182,183,195,503,808,810],[101,164,172,176,179,181,182,183,195,240,246,265,285,380,808,810],[101,164,172,176,179,181,182,183,195,233,808,810],[101,164,172,176,179,181,182,183,195,226,240,246,808,810],[101,164,172,176,179,181,182,183,195,412,808,810],[101,164,172,176,179,181,182,183,195,409,410,412,808,810],[101,164,172,176,179,181,182,183,195,409,411,503,808,810],[101,164,172,176,178,179,181,182,183,195,285,482,500,808,810],[101,164,172,176,178,179,181,182,183,195,356,359,375,380,500,808,810],[101,164,172,176,178,179,181,182,183,195,328,500,808,810],[101,164,172,176,179,181,182,183,195,388,808,810],[101,164,172,176,179,181,182,183,195,387,388,389,808,810],[101,164,172,176,179,181,182,183,195,387,808,810],[98,101,164,172,176,178,179,181,182,183,195,226,233,245,251,257,263,265,269,270,283,284,351,381,382,395,503,507,808,810],[101,164,172,176,179,181,182,183,195,230,233,267,304,401,402,407,503,555,808,810],[101,164,172,176,179,181,182,183,195,267,555,808,810],[101,164,172,176,179,181,182,183,195,230,284,453,503,555,808,810],[101,164,172,176,179,181,182,183,195,555,808,810],[101,164,172,176,179,181,182,183,195,233,267,268,555,808,810],[101,164,172,176,179,181,182,183,195,404,555,808,810],[101,164,172,176,179,181,182,183,195,270,383,386,393,808,810],[92,101,164,172,176,179,181,182,183,195,458,808,810],[101,164,172,176,179,181,182,183,193,195,240,255,808,810],[101,164,172,176,179,181,182,183,195,240,255,808,810],[92,101,164,172,176,179,181,182,183,195,325,808,810],[92,101,164,172,176,179,181,182,183,195,255,808,810],[92,101,164,172,176,179,181,182,183,195,246,255,458,808,810],[101,164,172,176,179,181,182,183,195,240,311,325,326,537,544,808,810],[101,164,172,176,179,181,182,183,195,310,538,539,540,541,543,808,810],[101,164,172,176,179,181,182,183,195,361,808,810],[101,164,172,176,179,181,182,183,195,361,362,808,810],[101,164,172,176,179,181,182,183,195,244,246,313,314,808,810],[101,164,172,176,179,181,182,183,195,246,320,321,808,810],[101,164,172,176,179,181,182,183,195,246,315,323,808,810],[101,164,172,176,179,181,182,183,195,320,808,810],[101,164,172,176,179,181,182,183,195,238,246,313,314,315,316,317,318,319,320,323,808,810],[101,164,172,176,179,181,182,183,195,246,313,320,321,322,324,808,810],[101,164,172,176,179,181,182,183,195,246,314,316,317,808,810],[101,164,172,176,179,181,182,183,195,314,316,319,321,808,810],[101,164,172,176,179,181,182,183,195,542,808,810],[101,164,172,176,179,181,182,183,195,246,808,810],[92,101,164,172,176,179,181,182,183,195,234,531,808,810],[92,101,164,172,176,179,181,182,183,195,212,808,810],[92,101,164,172,176,179,181,182,183,195,267,302,808,810],[92,101,164,172,176,179,181,182,183,195,267,395,808,810],[101,164,172,176,179,181,182,183,195,300,305,808,810],[92,101,164,172,176,179,181,182,183,195,301,509,808,810],[101,164,172,176,179,181,182,183,195,570,808,810],[92,96,101,164,172,176,178,179,181,182,183,195,221,222,223,224,225,507,551,575,808,810],[101,164,172,176,178,179,181,182,183,195,246,808,810],[101,164,172,176,178,179,181,182,183,195,245,250,331,348,390,391,395,450,452,503,504,808,810],[101,164,172,176,179,181,182,183,195,283,392,808,810],[101,164,172,176,179,181,182,183,195,507,808,810],[101,164,172,176,179,181,182,183,195,232,808,810],[92,101,164,172,176,179,181,182,183,195,237,240,455,471,473,808,810],[101,164,172,176,179,181,182,183,193,195,240,455,470,471,472,554,808,810],[101,164,172,176,179,181,182,183,195,464,465,466,467,468,469,808,810],[101,164,172,176,179,181,182,183,195,466,808,810],[101,164,172,176,179,181,182,183,195,470,808,810],[101,164,172,176,179,181,182,183,195,255,419,420,422,808,810],[92,101,164,172,176,179,181,182,183,195,246,413,414,415,416,421,808,810],[101,164,172,176,179,181,182,183,195,419,421,808,810],[101,164,172,176,179,181,182,183,195,417,808,810],[101,164,172,176,179,181,182,183,195,418,808,810],[92,101,164,172,176,179,181,182,183,195,255,301,509,808,810],[92,101,164,172,176,179,181,182,183,195,255,508,509,808,810],[92,101,164,172,176,179,181,182,183,195,255,509,808,810],[101,164,172,176,179,181,182,183,195,348,349,808,810],[101,164,172,176,179,181,182,183,195,349,808,810],[101,164,172,176,178,179,181,182,183,195,504,509,808,810],[101,164,172,176,179,181,182,183,195,378,808,810],[101,163,164,172,176,179,181,182,183,195,377,808,810],[101,164,172,176,179,181,182,183,195,240,246,252,254,356,369,373,375,452,455,492,493,500,504,808,810],[101,164,172,176,179,181,182,183,195,246,295,317,808,810],[101,164,172,176,179,181,182,183,195,356,367,370,375,808,810],[92,101,164,172,176,179,181,182,183,195,237,240,356,359,375,378,412,459,460,461,462,463,474,475,476,477,478,479,480,481,555,808,810],[101,164,172,176,179,181,182,183,195,237,240,265,356,363,364,365,368,369,808,810],[101,164,172,176,179,181,182,183,195,200,246,265,367,374,455,456,500,808,810],[101,164,172,176,179,181,182,183,195,371,808,810],[101,164,172,176,178,179,181,182,183,193,195,234,246,250,260,292,293,296,348,351,416,450,451,492,503,504,505,507,555,808,810],[101,164,172,176,179,181,182,183,195,237,238,240,808,810],[101,164,172,176,179,181,182,183,195,356,808,810],[101,163,164,172,176,179,181,182,183,195,265,292,293,350,351,352,353,354,355,504,808,810],[101,164,172,176,179,181,182,183,195,375,808,810],[101,163,164,172,176,179,181,182,183,195,239,240,250,254,290,356,363,364,365,366,367,370,371,372,373,374,493,808,810],[101,164,172,176,178,179,181,182,183,195,290,291,363,504,505,808,810],[101,164,172,176,179,181,182,183,195,265,293,348,351,356,452,504,808,810],[101,164,172,176,178,179,181,182,183,195,503,505,808,810],[101,164,172,176,178,179,181,182,183,195,200,500,504,505,808,810],[101,164,172,176,178,179,181,182,183,193,195,226,240,245,252,254,257,260,267,287,292,293,294,295,296,331,332,334,337,339,342,343,344,345,347,395,450,452,500,503,504,505,808,810],[101,164,172,176,178,179,181,182,183,195,200,808,810],[101,164,172,176,179,181,182,183,195,233,234,235,263,500,501,502,507,509,555,808,810],[101,164,172,176,179,181,182,183,195,230,231,503,808,810],[101,164,172,176,179,181,182,183,195,424,808,810],[101,164,172,176,178,179,181,182,183,195,200,212,242,408,412,413,414,415,416,422,423,555,808,810],[101,164,172,176,179,181,182,183,193,195,212,226,240,242,254,257,293,332,337,347,348,401,428,429,430,436,439,440,450,452,500,503,808,810],[101,164,172,176,179,181,182,183,195,257,263,270,283,293,351,503,808,810],[101,164,172,176,178,179,181,182,183,195,212,234,245,254,293,434,500,503,808,810],[101,164,172,176,179,181,182,183,195,454,808,810],[101,164,172,176,178,179,181,182,183,195,424,437,438,447,808,810],[101,164,172,176,179,181,182,183,195,500,503,808,810],[101,164,172,176,179,181,182,183,195,353,493,808,810],[101,164,172,176,179,181,182,183,195,254,292,395,509,808,810],[101,164,172,176,178,179,181,182,183,193,195,232,337,397,401,430,436,439,442,500,808,810],[101,164,172,176,178,179,181,182,183,195,270,283,401,443,808,810],[101,164,172,176,179,181,182,183,195,233,294,395,445,503,505,808,810],[101,164,172,176,178,179,181,182,183,195,212,416,503,808,810],[101,164,172,176,178,179,181,182,183,195,267,294,395,396,397,406,424,444,446,503,808,810],[98,101,164,172,176,178,179,181,182,183,195,292,449,507,509,808,810],[101,164,172,176,179,181,182,183,195,346,450,808,810],[101,164,172,176,178,179,181,182,183,193,195,240,243,245,246,252,254,260,269,270,283,293,296,332,334,344,347,348,395,428,429,430,431,433,435,450,452,500,509,808,810],[101,164,172,176,178,179,181,182,183,195,200,270,436,441,447,500,808,810],[101,164,172,176,179,181,182,183,195,273,274,275,276,277,278,279,280,281,282,808,810],[101,164,172,176,179,181,182,183,195,287,338,808,810],[101,164,172,176,179,181,182,183,195,340,808,810],[101,164,172,176,179,181,182,183,195,338,808,810],[101,164,172,176,179,181,182,183,195,340,341,808,810],[101,164,172,176,178,179,181,182,183,195,244,245,246,250,251,504,808,810],[101,164,172,176,178,179,181,182,183,193,195,232,234,252,256,292,295,296,330,450,500,505,507,509,808,810],[101,164,172,176,178,179,181,182,183,193,195,212,236,243,244,254,256,293,448,493,499,504,808,810],[101,164,172,176,179,181,182,183,195,363,808,810],[101,164,172,176,179,181,182,183,195,364,808,810],[101,164,172,176,179,181,182,183,195,246,257,492,808,810],[101,164,172,176,179,181,182,183,195,365,808,810],[101,164,172,176,179,181,182,183,195,239,808,810],[101,164,172,176,179,181,182,183,195,241,253,808,810],[101,164,172,176,178,179,181,182,183,195,241,245,252,808,810],[101,164,172,176,179,181,182,183,195,248,253,808,810],[101,164,172,176,179,181,182,183,195,249,808,810],[101,164,172,176,179,181,182,183,195,241,242,808,810],[101,164,172,176,179,181,182,183,195,241,297,808,810],[101,164,172,176,179,181,182,183,195,241,808,810],[101,164,172,176,179,181,182,183,195,243,287,336,808,810],[101,164,172,176,179,181,182,183,195,335,808,810],[101,164,172,176,179,181,182,183,195,240,242,243,808,810],[101,164,172,176,179,181,182,183,195,243,333,808,810],[101,164,172,176,179,181,182,183,195,240,242,808,810],[101,164,172,176,179,181,182,183,195,292,395,808,810],[101,164,172,176,179,181,182,183,195,492,808,810],[101,164,172,176,178,179,181,182,183,195,212,252,254,258,292,395,449,452,455,456,457,483,484,487,491,493,500,504,808,810],[101,164,172,176,179,181,182,183,195,306,309,311,312,325,326,808,810],[92,101,164,172,176,179,181,182,183,195,223,225,255,485,486,808,810],[92,101,164,172,176,179,181,182,183,195,223,225,255,485,486,490,808,810],[101,164,172,176,179,181,182,183,195,379,808,810],[101,164,172,176,179,181,182,183,195,265,286,291,292,356,357,358,359,360,362,375,376,378,381,449,452,503,505,808,810],[101,164,172,176,179,181,182,183,195,325,808,810],[101,164,172,176,178,179,181,182,183,195,330,500,808,810],[101,164,172,176,179,181,182,183,195,330,808,810],[101,164,172,176,178,179,181,182,183,195,252,298,327,329,331,449,500,507,509,808,810],[101,164,172,176,179,181,182,183,195,306,307,308,309,311,312,325,326,508,808,810],[98,101,164,172,176,178,179,181,182,183,193,195,212,241,242,254,260,292,293,296,395,447,448,450,500,503,504,507,808,810],[101,164,172,176,179,181,182,183,195,237,240,247,808,810],[101,164,172,176,179,181,182,183,195,291,293,425,428,808,810],[101,164,172,176,179,181,182,183,195,291,426,494,495,496,497,498,808,810],[101,164,172,176,178,179,181,182,183,195,287,503,808,810],[101,164,172,176,178,179,181,182,183,195,808,810],[101,164,172,176,179,181,182,183,195,290,375,808,810],[101,164,172,176,179,181,182,183,195,289,808,810],[101,164,172,176,179,181,182,183,195,291,344,808,810],[101,164,172,176,179,181,182,183,195,288,290,503,808,810],[101,164,172,176,178,179,181,182,183,195,236,291,425,426,427,500,503,504,808,810],[92,101,164,172,176,179,181,182,183,195,240,246,324,808,810],[92,101,164,172,176,179,181,182,183,195,238,808,810],[101,164,172,176,179,181,182,183,195,228,229,808,810],[92,101,164,172,176,179,181,182,183,195,234,808,810],[92,101,164,172,176,179,181,182,183,195,240,310,808,810],[92,98,101,164,172,176,179,181,182,183,195,292,296,507,509,808,810],[101,164,172,176,179,181,182,183,195,234,531,532,808,810],[92,101,164,172,176,179,181,182,183,195,305,808,810],[92,101,164,172,176,179,181,182,183,193,195,212,232,299,301,303,304,509,808,810],[101,164,172,176,179,181,182,183,195,240,267,504,808,810],[101,164,172,176,179,181,182,183,195,240,432,808,810],[92,101,164,172,176,178,179,181,182,183,193,195,230,232,305,403,507,508,808,810],[92,101,164,172,176,179,181,182,183,195,221,222,223,224,225,507,552,575,808,810],[92,93,94,95,96,101,164,172,176,179,181,182,183,195,808,810],[101,164,172,176,179,181,182,183,195,398,399,400,808,810],[101,164,172,176,179,181,182,183,195,398,808,810],[92,96,101,164,172,176,178,179,180,181,182,183,193,195,220,221,222,223,224,225,226,232,260,265,442,470,505,506,509,552,575,808,810],[101,164,172,176,179,181,182,183,195,517,808,810],[101,164,172,176,179,181,182,183,195,519,808,810],[101,164,172,176,179,181,182,183,195,523,808,810],[101,164,172,176,179,181,182,183,195,571,808,810],[101,164,172,176,179,181,182,183,195,525,808,810],[101,164,172,176,179,181,182,183,195,527,528,529,808,810],[101,164,172,176,179,181,182,183,195,533,808,810],[97,101,164,172,176,179,181,182,183,195,511,516,518,520,524,526,530,534,536,546,547,549,553,554,555,556,808,810],[101,164,172,176,179,181,182,183,195,535,808,810],[101,164,172,176,179,181,182,183,195,545,808,810],[101,164,172,176,179,181,182,183,195,301,808,810],[101,164,172,176,179,181,182,183,195,548,808,810],[101,163,164,172,176,179,181,182,183,195,291,425,426,428,494,495,497,498,550,552,808,810],[101,164,172,176,179,181,182,183,195,220,808,810],[92,101,164,172,176,179,181,182,183,195,616,627,632,638,639,646,648,649,651,692,695,808,810],[92,101,164,172,176,179,181,182,183,195,616,627,632,637,639,648,652,653,655,656,692,695,808,810],[92,101,164,172,176,179,181,182,183,195,648,653,697,808,810],[92,101,164,172,176,179,181,182,183,195,631,695,808,810],[92,101,164,172,176,179,181,182,183,195,615,616,618,627,695,808,810],[92,101,164,172,176,179,181,182,183,195,616,627,648,686,695,808,810],[92,101,164,172,176,179,181,182,183,195,616,654,675,679,695,808,810],[92,101,164,172,176,179,181,182,183,195,639,662,663,695,736,808,810],[101,164,172,176,179,181,182,183,195,615,695,808,810],[101,164,172,176,179,181,182,183,195,627,695,808,810],[92,101,164,172,176,179,181,182,183,195,616,627,632,638,639,692,695,808,810],[92,101,164,172,176,179,181,182,183,195,616,618,653,667,719,808,810],[92,101,164,172,176,179,181,182,183,195,614,616,618,667,808,810],[92,101,164,172,176,179,181,182,183,195,616,618,647,667,668,695,808,810],[92,101,164,172,176,179,181,182,183,195,616,627,630,634,638,639,663,677,678,692,695,808,810],[92,101,164,172,176,179,181,182,183,195,620,627,695,808,810],[92,101,164,172,176,179,181,182,183,195,620,627,692,695,808,810],[92,101,164,172,176,179,181,182,183,195,695,808,810],[92,101,164,172,176,179,181,182,183,195,653,663,695,808,810],[92,101,164,172,176,179,181,182,183,195,615,663,695,808,810],[92,101,164,172,176,179,181,182,183,195,663,695,808,810],[92,101,164,172,176,179,181,182,183,195,628,808,810],[92,101,164,172,176,179,181,182,183,195,616,663,695,808,810],[92,101,164,172,176,179,181,182,183,195,614,616,695,808,810],[92,101,164,172,176,179,181,182,183,195,615,616,617,695,808,810],[92,101,164,172,176,179,181,182,183,195,615,616,618,695,747,808,810],[92,101,164,172,176,179,181,182,183,195,640,641,642,808,810],[92,101,164,172,176,179,181,182,183,195,627,629,630,641,663,695,698,808,810],[101,164,172,176,179,181,182,183,195,685,695,808,810],[101,164,172,176,179,181,182,183,195,627,628,647,690,692,695,808,810],[101,164,172,176,179,181,182,183,195,614,615,616,618,619,620,627,628,630,638,639,640,643,647,650,653,654,663,667,669,675,677,678,679,680,687,690,691,692,695,696,697,699,700,701,702,703,704,705,706,708,710,712,713,714,715,716,717,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,742,743,744,745,746,808,810],[92,101,164,172,176,179,181,182,183,195,616,632,639,658,660,695,711,808,810],[92,101,164,172,176,179,181,182,183,195,616,620,627,668,695,709,808,810],[92,101,164,172,176,179,181,182,183,195,616,627,808,810],[92,101,164,172,176,179,181,182,183,195,616,620,627,668,695,707,808,810],[92,101,164,172,176,179,181,182,183,195,616,639,647,659,668,695,808,810],[92,101,164,172,176,179,181,182,183,195,616,627,632,637,639,648,692,695,703,711,714,808,810],[92,101,164,172,176,179,181,182,183,195,637,695,808,810],[92,101,164,172,176,179,181,182,183,195,652,695,808,810],[101,164,172,176,179,181,182,183,195,621,626,695,808,810],[101,164,172,176,179,181,182,183,195,619,620,621,626,692,695,808,810],[101,164,172,176,179,181,182,183,195,621,626,631,808,810],[101,164,172,176,179,181,182,183,195,621,626,662,680,695,808,810],[101,164,172,176,179,181,182,183,195,621,626,627,632,633,634,651,656,657,660,661,695,808,810],[101,164,172,176,179,181,182,183,195,621,626,640,643,695,808,810],[101,164,172,176,179,181,182,183,195,621,626,663,695,808,810],[101,164,172,176,179,181,182,183,195,621,626,627,808,810],[101,164,172,176,179,181,182,183,195,621,626,808,810],[101,164,172,176,179,181,182,183,195,621,626,627,666,667,669,808,810],[101,164,172,176,179,181,182,183,195,621,626,627,666,695,808,810],[101,164,172,176,179,181,182,183,195,621,626,628,650,695,808,810],[101,164,172,176,179,181,182,183,195,646,662,685,695,808,810],[101,164,172,176,179,181,182,183,195,627,632,645,646,647,662,670,673,681,685,687,688,689,691,695,808,810],[101,164,172,176,179,181,182,183,195,627,632,645,646,808,810],[101,164,172,176,179,181,182,183,195,685,808,810],[101,164,172,176,179,181,182,183,195,626,627,632,644,662,663,664,665,670,671,672,673,674,681,682,683,684,808,810],[101,164,172,176,179,181,182,183,195,621,626,627,629,630,662,695,808,810],[101,164,172,176,179,181,182,183,195,632,645,650,662,695,808,810],[101,164,172,176,179,181,182,183,195,645,655,662,808,810],[101,164,172,176,179,181,182,183,195,632,662,695,808,810],[92,101,164,172,176,179,181,182,183,195,630,658,659,662,695,808,810],[101,164,172,176,179,181,182,183,195,662,808,810],[101,164,172,176,179,181,182,183,195,645,662,808,810],[101,164,172,176,179,181,182,183,195,630,632,662,695,808,810],[101,164,172,176,179,181,182,183,195,648,662,695,808,810],[101,164,172,176,179,181,182,183,195,663,695,808,810],[92,101,164,172,176,179,181,182,183,195,653,654,695,808,810],[101,164,172,176,179,181,182,183,195,630,637,644,646,647,663,692,695,808,810],[92,101,164,172,176,179,181,182,183,195,650,654,675,679,695,722,723,724,737,808,810],[92,101,164,172,176,179,181,182,183,195,695,708,710,712,713,715,808,810],[101,164,172,176,179,181,182,183,195,695,808,810],[92,101,164,172,176,179,181,182,183,195,715,808,810],[101,164,172,176,179,181,182,183,195,627,695,741,808,810],[101,164,172,176,179,181,182,183,195,620,695,808,810],[92,101,164,172,176,179,181,182,183,195,662,676,679,695,808,810],[101,164,172,176,179,181,182,183,195,637,645,648,662,808,810],[92,101,164,172,176,179,181,182,183,195,658,718,808,810],[92,101,164,172,176,179,181,182,183,195,613,614,615,618,619,620,627,628,629,632,650,658,692,693,694,747,808,810],[101,164,172,176,179,181,182,183,195,621,808,810],[101,164,172,176,179,181,182,183,195,200,220,808,810],[101,116,119,122,123,164,172,176,179,181,182,183,195,212,808,810],[101,119,164,172,176,179,181,182,183,195,200,212,808,810],[101,119,123,164,172,176,179,181,182,183,195,212,808,810],[101,164,172,176,179,181,182,183,195,200,808,810],[101,113,164,172,176,179,181,182,183,195,808,810],[101,117,164,172,176,179,181,182,183,195,808,810],[101,115,116,119,164,172,176,179,181,182,183,195,212,808,810],[101,164,172,176,179,181,182,183,185,195,209,808,810],[101,113,164,172,176,179,181,182,183,195,220,808,810],[101,115,119,164,172,176,179,181,182,183,185,195,212,808,810],[101,110,111,112,114,118,164,172,175,176,179,181,182,183,195,200,212,808,810],[101,119,128,136,164,172,176,179,181,182,183,195,808,810],[101,111,117,164,172,176,179,181,182,183,195,808,810],[101,119,145,146,164,172,176,179,181,182,183,195,808,810],[101,111,114,119,164,172,176,179,181,182,183,195,203,212,220,808,810],[101,119,164,172,176,179,181,182,183,195,808,810],[101,115,119,164,172,176,179,181,182,183,195,212,808,810],[101,110,164,172,176,179,181,182,183,195,808,810],[101,113,114,115,117,118,119,120,121,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,146,147,148,149,150,164,172,176,179,181,182,183,195,808,810],[101,119,138,141,164,172,176,179,181,182,183,195,808,810],[101,119,128,129,130,164,172,176,179,181,182,183,195,808,810],[101,117,119,129,131,164,172,176,179,181,182,183,195,808,810],[101,118,164,172,176,179,181,182,183,195,808,810],[101,111,113,119,164,172,176,179,181,182,183,195,808,810],[101,119,123,129,131,164,172,176,179,181,182,183,195,808,810],[101,123,164,172,176,179,181,182,183,195,808,810],[101,117,119,122,164,172,176,179,181,182,183,195,212,808,810],[101,111,115,119,128,164,172,176,179,181,182,183,195,808,810],[101,119,138,164,172,176,179,181,182,183,195,808,810],[101,131,164,172,176,179,181,182,183,195,808,810],[101,113,119,145,164,172,176,179,181,182,183,195,203,218,220,808,810],[101,164,172,176,179,181,182,183,195,612,808,810],[101,164,172,176,179,181,182,183,195,636,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,603,604,607,808,810],[92,101,164,172,176,179,181,182,183,195,255,564,568,588,609,610,748,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,546,564,567,568,578,588,609,751,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,546,564,578,588,609,755,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,564,568,578,588,610,750,752,808,810],[92,101,164,172,176,179,181,182,183,195,255,546,578,588,757,758,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,578,588,755,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,546,564,567,568,578,588,598,609,751,808,810],[92,101,164,172,176,179,181,182,183,195,255,564,568,588,610,750,761,808,810],[101,164,172,176,179,181,182,183,195,255,588,808,810],[92,101,164,172,176,179,181,182,183,195,255,534,536,588,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,578,588,808,810],[101,164,172,176,179,181,182,183,195,255,557,764,808,810],[92,101,164,172,176,179,181,182,183,195,255,534,536,578,588,766,767,768,769,808,810],[101,164,172,176,179,181,182,183,195,255,554,557,572,595,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,546,573,578,588,755,808,810],[101,164,172,176,179,181,182,183,195,255,536,808,810],[92,101,164,172,176,179,181,182,183,195,255,578,588,751,772,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,578,588,751,755,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,578,588,610,776,808,810],[101,164,172,176,179,181,182,183,195,255,536,546,567,568,598,602,751,783,808,810],[92,101,164,172,176,179,181,182,183,195,255,782,808,810],[92,101,164,172,176,179,181,182,183,195,255,546,564,567,569,578,785,790,791,793,794,795,796,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,578,778,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,567,578,588,755,786,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,578,588,798,808,810],[101,164,172,176,179,181,182,183,195,255,747,808,810],[92,101,164,172,176,179,181,182,183,195,255,546,573,808,810],[92,101,164,172,176,179,181,182,183,195,255,546,563,808,810],[101,164,172,176,179,181,182,183,195,255,534,567,808,810],[101,164,172,176,179,181,182,183,195,255,536,568,598,609,751,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,588,751,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,567,578,588,808,810],[92,101,164,172,176,179,181,182,183,195,255,564,568,598,602,808,810],[92,101,164,172,176,179,181,182,183,195,255,564,567,568,808,810],[101,164,172,176,179,181,182,183,195,255,568,606,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,808,810],[101,164,172,176,179,181,182,183,195,255,567,587,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,780,781,808,810],[101,164,172,176,179,181,182,183,195,255,536,567,568,578,588,598,609,751,808,810],[92,101,164,172,176,179,181,182,183,195,255,562,589,590,593,808,810],[92,101,164,172,176,179,181,182,183,195,255,546,562,567,588,592,808,810],[101,164,172,176,179,181,182,183,195,255,567,588,808,810],[101,164,172,176,179,181,182,183,195,255,546,573,574,594,808,810],[92,101,164,172,176,179,181,182,183,195,255,536,546,567,578,587,588,808,810],[101,164,172,176,179,181,182,183,195,255,567,588,751,808,810],[101,164,172,176,179,181,182,183,195,255,567,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,577,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,575,577,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,588,592,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,601,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,588,787,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,805,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,586,808,810],[92,101,164,172,176,179,181,182,183,195,255,546,564,567,568,578,786,788,808,810],[92,101,164,172,176,179,181,182,183,195,255,564,567,569,578,588,602,808,810],[101,164,172,176,179,181,182,183,195,255,536,567,568,606,808,810],[92,101,164,172,176,179,181,182,183,195,255,546,567,568,598,605,808,810],[92,101,164,172,176,179,181,182,183,195,255,564,567,569,578,588,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,569,578,588,755,786,808,810],[101,164,172,176,179,181,182,183,195,255,567,569,578,588,786,788,789,808,810],[101,164,172,176,179,181,182,183,195,255,567,569,578,588,808,810],[101,164,172,176,179,181,182,183,195,255,567,569,578,588,792,808,810],[92,101,164,172,176,179,181,182,183,195,255,567,588,808,810],[101,164,172,176,179,181,182,183,195,255,563,808,810],[101,164,172,176,179,181,182,183,195,255,565,566,808,810]],"fileInfos":[{"version":"bcd24271a113971ba9eb71ff8cb01bc6b0f872a85c23fdbe5d93065b375933cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f88bedbeb09c6f5a6645cb24c7c55f1aa22d19ae96c8e6959cbd8b85a707bc6","impliedFormat":1},{"version":"7fe93b39b810eadd916be8db880dd7f0f7012a5cc6ffb62de8f62a2117fa6f1f","impliedFormat":1},{"version":"bb0074cc08b84a2374af33d8bf044b80851ccc9e719a5e202eacf40db2c31600","impliedFormat":1},{"version":"1a7daebe4f45fb03d9ec53d60008fbf9ac45a697fdc89e4ce218bc94b94f94d6","impliedFormat":1},{"version":"f94b133a3cb14a288803be545ac2683e0d0ff6661bcd37e31aaaec54fc382aed","impliedFormat":1},{"version":"f59d0650799f8782fd74cf73c19223730c6d1b9198671b1c5b3a38e1188b5953","impliedFormat":1},{"version":"8a15b4607d9a499e2dbeed9ec0d3c0d7372c850b2d5f1fb259e8f6d41d468a84","impliedFormat":1},{"version":"26e0fe14baee4e127f4365d1ae0b276f400562e45e19e35fd2d4c296684715e6","impliedFormat":1},{"version":"1e9332c23e9a907175e0ffc6a49e236f97b48838cc8aec9ce7e4cec21e544b65","impliedFormat":1},{"version":"3753fbc1113dc511214802a2342280a8b284ab9094f6420e7aa171e868679f91","impliedFormat":1},{"version":"999ca32883495a866aa5737fe1babc764a469e4cde6ee6b136a4b9ae68853e4b","impliedFormat":1},{"version":"17f13ecb98cbc39243f2eee1f16d45cd8ec4706b03ee314f1915f1a8b42f6984","impliedFormat":1},{"version":"d6b1eba8496bdd0eed6fc8a685768fe01b2da4a0388b5fe7df558290bffcf32f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"eadcffda2aa84802c73938e589b9e58248d74c59cb7fcbca6474e3435ac15504","affectsGlobalScope":true,"impliedFormat":1},{"version":"105ba8ff7ba746404fe1a2e189d1d3d2e0eb29a08c18dded791af02f29fb4711","affectsGlobalScope":true,"impliedFormat":1},{"version":"00343ca5b2e3d48fa5df1db6e32ea2a59afab09590274a6cccb1dbae82e60c7c","affectsGlobalScope":true,"impliedFormat":1},{"version":"ebd9f816d4002697cb2864bea1f0b70a103124e18a8cd9645eeccc09bdf80ab4","affectsGlobalScope":true,"impliedFormat":1},{"version":"2c1afac30a01772cd2a9a298a7ce7706b5892e447bb46bdbeef720f7b5da77ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"7b0225f483e4fa685625ebe43dd584bb7973bbd84e66a6ba7bbe175ee1048b4f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0a4b8ac6ce74679c1da2b3795296f5896e31c38e888469a8e0f99dc3305de60","affectsGlobalScope":true,"impliedFormat":1},{"version":"3084a7b5f569088e0146533a00830e206565de65cae2239509168b11434cd84f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5079c53f0f141a0698faa903e76cb41cd664e3efb01cc17a5c46ec2eb0bef42","affectsGlobalScope":true,"impliedFormat":1},{"version":"32cafbc484dea6b0ab62cf8473182bbcb23020d70845b406f80b7526f38ae862","affectsGlobalScope":true,"impliedFormat":1},{"version":"fca4cdcb6d6c5ef18a869003d02c9f0fd95df8cfaf6eb431cd3376bc034cad36","affectsGlobalScope":true,"impliedFormat":1},{"version":"b93ec88115de9a9dc1b602291b85baf825c85666bf25985cc5f698073892b467","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5c06dcc3fe849fcb297c247865a161f995cc29de7aa823afdd75aaaddc1419b","affectsGlobalScope":true,"impliedFormat":1},{"version":"b77e16112127a4b169ef0b8c3a4d730edf459c5f25fe52d5e436a6919206c4d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"fbffd9337146eff822c7c00acbb78b01ea7ea23987f6c961eba689349e744f8c","affectsGlobalScope":true,"impliedFormat":1},{"version":"a995c0e49b721312f74fdfb89e4ba29bd9824c770bbb4021d74d2bf560e4c6bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"c7b3542146734342e440a84b213384bfa188835537ddbda50d30766f0593aff9","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce6180fa19b1cccd07ee7f7dbb9a367ac19c0ed160573e4686425060b6df7f57","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f02e2476bccb9dbe21280d6090f0df17d2f66b74711489415a8aa4df73c9675","affectsGlobalScope":true,"impliedFormat":1},{"version":"45e3ab34c1c013c8ab2dc1ba4c80c780744b13b5676800ae2e3be27ae862c40c","affectsGlobalScope":true,"impliedFormat":1},{"version":"805c86f6cca8d7702a62a844856dbaa2a3fd2abef0536e65d48732441dde5b5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e42e397f1a5a77994f0185fd1466520691456c772d06bf843e5084ceb879a0ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"f4c2b41f90c95b1c532ecc874bd3c111865793b23aebcc1c3cbbabcd5d76ffb0","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab26191cfad5b66afa11b8bf935ef1cd88fabfcb28d30b2dfa6fad877d050332","affectsGlobalScope":true,"impliedFormat":1},{"version":"2088bc26531e38fb05eedac2951480db5309f6be3fa4a08d2221abb0f5b4200d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb9d366c425fea79716a8fb3af0d78e6b22ebbab3bd64d25063b42dc9f531c1e","affectsGlobalScope":true,"impliedFormat":1},{"version":"500934a8089c26d57ebdb688fc9757389bb6207a3c8f0674d68efa900d2abb34","affectsGlobalScope":true,"impliedFormat":1},{"version":"689da16f46e647cef0d64b0def88910e818a5877ca5379ede156ca3afb780ac3","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc21cc8b6fee4f4c2440d08035b7ea3c06b3511314c8bab6bef7a92de58a2593","affectsGlobalScope":true,"impliedFormat":1},{"version":"7ca53d13d2957003abb47922a71866ba7cb2068f8d154877c596d63c359fed25","affectsGlobalScope":true,"impliedFormat":1},{"version":"54725f8c4df3d900cb4dac84b64689ce29548da0b4e9b7c2de61d41c79293611","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5594bc3076ac29e6c1ebda77939bc4c8833de72f654b6e376862c0473199323","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f3eb332c2d73e729f3364fcc0c2b375e72a121e8157d25a82d67a138c83a95c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6f4427f9642ce8d500970e4e69d1397f64072ab73b97e476b4002a646ac743b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"48915f327cd1dea4d7bd358d9dc7732f58f9e1626a29cc0c05c8c692419d9bb7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7bf9377723203b5a6a4b920164df22d56a43f593269ba6ae1fdc97774b68855","affectsGlobalScope":true,"impliedFormat":1},{"version":"db9709688f82c9e5f65a119c64d835f906efe5f559d08b11642d56eb85b79357","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b25b8c874acd1a4cf8444c3617e037d444d19080ac9f634b405583fd10ce1f7","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be57d7c90cf1f8112ee2636a068d8fd181289f82b744160ec56a7dc158a9f5","affectsGlobalScope":true,"impliedFormat":1},{"version":"a917a49ac94cd26b754ab84e113369a75d1a47a710661d7cd25e961cc797065f","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d3261badeb7843d157ef3e6f5d1427d0eeb0af0cf9df84a62cfd29fd47ac86e","affectsGlobalScope":true,"impliedFormat":1},{"version":"195daca651dde22f2167ac0d0a05e215308119a3100f5e6268e8317d05a92526","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b11e4285cd2bb164a4dc09248bdec69e9842517db4ca47c1ba913011e44ff2f","affectsGlobalScope":true,"impliedFormat":1},{"version":"0508571a52475e245b02bc50fa1394065a0a3d05277fbf5120c3784b85651799","affectsGlobalScope":true,"impliedFormat":1},{"version":"8f9af488f510c3015af3cc8c267a9e9d96c4dd38a1fdff0e11dc5a544711415b","affectsGlobalScope":true,"impliedFormat":1},{"version":"fc611fea8d30ea72c6bbfb599c9b4d393ce22e2f5bfef2172534781e7d138104","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bd714129fca875f7d4c477a1a392200b0bcd13fb2e80928cd334b63830ea047","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2c9037ae6cd2c52d80ceef0b3c5ffdb488627d71529cf4f63776daf11161c9a","affectsGlobalScope":true,"impliedFormat":1},{"version":"135d5cf4d345f59f1a9caadfafcd858d3d9cc68290db616cc85797224448cccc","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc238c3f81c2984751932b6aab223cd5b830e0ac6cad76389e5e9d2ffc03287d","affectsGlobalScope":true,"impliedFormat":1},{"version":"4a07f9b76d361f572620927e5735b77d6d2101c23cdd94383eb5b706e7b36357","affectsGlobalScope":true,"impliedFormat":1},{"version":"7c4e8dc6ab834cc6baa0227e030606d29e3e8449a9f67cdf5605ea5493c4db29","affectsGlobalScope":true,"impliedFormat":1},{"version":"de7ba0fd02e06cd9a5bd4ab441ed0e122735786e67dde1e849cced1cd8b46b78","affectsGlobalScope":true,"impliedFormat":1},{"version":"6148e4e88d720a06855071c3db02069434142a8332cf9c182cda551adedf3156","affectsGlobalScope":true,"impliedFormat":1},{"version":"d63dba625b108316a40c95a4425f8d4294e0deeccfd6c7e59d819efa19e23409","affectsGlobalScope":true,"impliedFormat":1},{"version":"0568d6befee03dd435bed4fc25c4e46865b24bdcb8c563fdc21f580a2c301904","affectsGlobalScope":true,"impliedFormat":1},{"version":"30d62269b05b584741f19a5369852d5d34895aa2ac4fd948956f886d15f9cc0d","affectsGlobalScope":true,"impliedFormat":1},{"version":"f128dae7c44d8f35ee42e0a437000a57c9f06cc04f8b4fb42eebf44954d53dc8","affectsGlobalScope":true,"impliedFormat":1},{"version":"ffbe6d7b295306b2ba88030f65b74c107d8d99bdcf596ea99c62a02f606108b0","affectsGlobalScope":true,"impliedFormat":1},{"version":"996fb27b15277369c68a4ba46ed138b4e9e839a02fb4ec756f7997629242fd9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"79b712591b270d4778c89706ca2cfc56ddb8c3f895840e477388f1710dc5eda9","affectsGlobalScope":true,"impliedFormat":1},{"version":"20884846cef428b992b9bd032e70a4ef88e349263f63aeddf04dda837a7dba26","affectsGlobalScope":true,"impliedFormat":1},{"version":"5fcab789c73a97cd43828ee3cc94a61264cf24d4c44472ce64ced0e0f148bdb2","affectsGlobalScope":true,"impliedFormat":1},{"version":"db59a81f070c1880ad645b2c0275022baa6a0c4f0acdc58d29d349c6efcf0903","affectsGlobalScope":true,"impliedFormat":1},{"version":"673294292640f5722b700e7d814e17aaf7d93f83a48a2c9b38f33cbc940ad8b0","affectsGlobalScope":true,"impliedFormat":1},{"version":"d786b48f934cbca483b3c6d0a798cb43bbb4ada283e76fb22c28e53ae05b9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ecb8e347cb6b2a8927c09b86263663289418df375f5e68e11a0ae683776978f","affectsGlobalScope":true,"impliedFormat":1},{"version":"142efd4ce210576f777dc34df121777be89eda476942d6d6663b03dcb53be3ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"379bc41580c2d774f82e828c70308f24a005b490c25ba34d679d84bcf05c3d9d","affectsGlobalScope":true,"impliedFormat":1},{"version":"ed484fb2aa8a1a23d0277056ec3336e0a0b52f9b8d6a961f338a642faf43235d","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ffedae1d1c2d53fdbca1c96d3c7dda544281f7d262f99b6880634f8fd8d9820","affectsGlobalScope":true,"impliedFormat":1},{"version":"83a730b125d477dd264df8ba479afab27a3dae7152b005c214ab94dc7ee44fd3","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ce14b81c5cc821994aa8ec1d42b220dd41b27fcc06373bce3958af7421b77d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3a048b3e9302ef9a34ef4ebb9aecfb28b66abb3bce577206a79fee559c230da","affectsGlobalScope":true,"impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"dc0a7f107690ee5cd8afc8dbf05c4df78085471ce16bdd9881642ec738bc81fe","impliedFormat":1},{"version":"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","impliedFormat":1},{"version":"d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","impliedFormat":1},{"version":"643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","impliedFormat":1},{"version":"21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75","impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ccdaa19852d25ecd84eec365c3bfa16e7859cadecf6e9ca6d0dbbbee439743f","affectsGlobalScope":true,"impliedFormat":1},{"version":"438b41419b1df9f1fbe33b5e1b18f5853432be205991d1b19f5b7f351675541e","affectsGlobalScope":true,"impliedFormat":1},{"version":"096116f8fedc1765d5bd6ef360c257b4a9048e5415054b3bf3c41b07f8951b0b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5e01375c9e124a83b52ee4b3244ed1a4d214a6cfb54ac73e164a823a4a7860a","affectsGlobalScope":true,"impliedFormat":1},{"version":"f90ae2bbce1505e67f2f6502392e318f5714bae82d2d969185c4a6cecc8af2fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b58e207b93a8f1c88bbf2a95ddc686ac83962b13830fe8ad3f404ffc7051fb4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1fefabcb2b06736a66d2904074d56268753654805e829989a46a0161cd8412c5","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"c18a99f01eb788d849ad032b31cafd49de0b19e083fe775370834c5675d7df8e","affectsGlobalScope":true,"impliedFormat":1},{"version":"5247874c2a23b9a62d178ae84f2db6a1d54e6c9a2e7e057e178cc5eea13757fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338","impliedFormat":1},{"version":"10ec5e82144dfac6f04fa5d1d6c11763b3e4dbbac6d99101427219ab3e2ae887","impliedFormat":1},{"version":"615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c","impliedFormat":1},{"version":"074de5b2fdead0165a2757e3aaef20f27a6347b1c36adea27d51456795b37682","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"24371e69a38fc33e268d4a8716dbcda430d6c2c414a99ff9669239c4b8f40dea","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"3e11fce78ad8c0e1d1db4ba5f0652285509be3acdd519529bc8fcef85f7dafd9","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"9c32412007b5662fd34a8eb04292fb5314ec370d7016d1c2fb8aa193c807fe22","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a","impliedFormat":1},{"version":"6ebe8ebb8659aaa9d1acbf3710d7dae3e923e97610238b9511c25dc39023a166","impliedFormat":1},{"version":"e85d7f8068f6a26710bff0cc8c0fc5e47f71089c3780fbede05857331d2ddec9","impliedFormat":1},{"version":"7befaf0e76b5671be1d47b77fcc65f2b0aad91cc26529df1904f4a7c46d216e9","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214","impliedFormat":1},{"version":"c63b9ada8c72f95aac5db92aea07e5e87ec810353cdf63b2d78f49a58662cf6c","impliedFormat":1},{"version":"1cc2a09e1a61a5222d4174ab358a9f9de5e906afe79dbf7363d871a7edda3955","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"b64d4d1c5f877f9c666e98e833f0205edb9384acc46e98a1fef344f64d6aba44","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"c9381908473a1c92cb8c516b184e75f4d226dad95c3a85a5af35f670064d9a2f","impliedFormat":1},{"version":"c3f5289820990ab66b70c7fb5b63cb674001009ff84b13de40619619a9c8175f","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3275d55fac10b799c9546804126239baf020d220136163f763b55a74e50e750","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa68a0a3b7cb32c00e39ee3cd31f8f15b80cac97dce51b6ee7fc14a1e8deb30b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c36e755bced82df7fb6ce8169265d0a7bb046ab4e2cb6d0da0cb72b22033e89","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7a93de4ff8a63bafe62ba86b89af1df0ccb5e40bb85b0c67d6bbcfdcf96bf3d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"90e85f9bc549dfe2b5749b45fe734144e96cd5d04b38eae244028794e142a77e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e0a5deeb610b2a50a6350bd23df6490036a1773a8a71d70f2f9549ab009e67ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fad5618174d74a34ee006406d4eb37e8d07dd62eb1315dbf52f48d31a337547","impliedFormat":1},{"version":"7e49f52a159435fc8df4de9dc377ef5860732ca2dc9efec1640531d3cf5da7a3","impliedFormat":1},{"version":"dd4bde4bdc2e5394aed6855e98cf135dfdf5dd6468cad842e03116d31bbcc9bc","impliedFormat":1},{"version":"4d4e879009a84a47c05350b8dca823036ba3a29a3038efed1be76c9f81e45edf","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b50a819485ffe0d237bf0d131e92178d14d11e2aa873d73615a9ec578b341f5","impliedFormat":1},{"version":"9ba13b47cb450a438e3076c4a3f6afb9dc85e17eae50f26d4b2d72c0688c9251","impliedFormat":1},{"version":"b64cd4401633ea4ecadfd700ddc8323a13b63b106ac7127c1d2726f32424622c","impliedFormat":1},{"version":"37c6e5fe5715814412b43cc9b50b24c67a63c4e04e753e0d1305970d65417a60","impliedFormat":1},{"version":"1d024184fb57c58c5c91823f9d10b4915a4867b7934e89115fd0d861a9df27c8","impliedFormat":1},{"version":"ee0e4946247f842c6dd483cbb60a5e6b484fee07996e3a7bc7343dfb68a04c5d","impliedFormat":1},{"version":"ef051f42b7e0ef5ca04552f54c4552eac84099d64b6c5ad0ef4033574b6035b8","impliedFormat":1},{"version":"853a43154f1d01b0173d9cbd74063507ece57170bad7a3b68f3fa1229ad0a92f","impliedFormat":1},{"version":"56231e3c39a031bfb0afb797690b20ed4537670c93c0318b72d5180833d98b72","impliedFormat":1},{"version":"5cc7c39031bfd8b00ad58f32143d59eb6ffc24f5d41a20931269011dccd36c5e","impliedFormat":1},{"version":"12d602a8fe4c2f2ba4f7804f5eda8ba07e0c83bf5cf0cda8baffa2e9967bfb77","affectsGlobalScope":true,"impliedFormat":1},{"version":"a856ab781967b62b288dfd85b860bef0e62f005ed4b1b8fa25c53ce17856acaf","impliedFormat":1},{"version":"cc25940cfb27aa538e60d465f98bb5068d4d7d33131861ace43f04fe6947d68f","impliedFormat":1},{"version":"8db46b61a690f15b245cf16270db044dc047dce9f93b103a59f50262f677ea1f","impliedFormat":1},{"version":"01ff95aa1443e3f7248974e5a771f513cb2ac158c8898f470a1792f817bee497","impliedFormat":1},{"version":"757227c8b345c57d76f7f0e3bbad7a91ffca23f1b2547cbed9e10025816c9cb7","impliedFormat":1},{"version":"959d0327c96dd9bb5521f3ed6af0c435996504cc8dd46baa8e12cb3b3518cef1","impliedFormat":1},{"version":"e1c1a0b4d1ead0de9eca52203aeb1f771f21e6238d6fcd15aa56ac2a02f1b7bf","impliedFormat":1},{"version":"101f482fd48cb4c7c0468dcc6d62c843d842977aea6235644b1edd05e81fbf22","impliedFormat":1},{"version":"266bee0a41e9c3ba335583e21e9277ae03822402cf5e8e1d99f5196853613b98","affectsGlobalScope":true,"impliedFormat":1},{"version":"ee96415bb64198cc13555da26474825a638e48e5a3c03cb33dd82b7e68fcc417","impliedFormat":1},{"version":"3ef397f12387eff17f550bc484ea7c27d21d43816bbe609d495107f44b97e933","impliedFormat":1},{"version":"1023282e2ba810bc07905d3668349fbd37a26411f0c8f94a70ef3c05fe523fcf","impliedFormat":1},{"version":"b214ebcf76c51b115453f69729ee8aa7b7f8eccdae2a922b568a45c2d7ff52f7","impliedFormat":1},{"version":"429c9cdfa7d126255779efd7e6d9057ced2d69c81859bbab32073bad52e9ba76","impliedFormat":1},{"version":"e236b5eba291f51bdf32c231673e6cab81b5410850e61f51a7a524dddadc0f95","impliedFormat":1},{"version":"ce8653341224f8b45ff46d2a06f2cacb96f841f768a886c9d8dd8ec0878b11bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f2c62938251b45715fd2a9887060ec4fbc8724727029d1cbce373747252bdd7","impliedFormat":1},{"version":"e3ace08b6bbd84655d41e244677b474fd995923ffef7149ddb68af8848b60b05","impliedFormat":1},{"version":"132580b0e86c48fab152bab850fc57a4b74fe915c8958d2ccb052b809a44b61c","impliedFormat":1},{"version":"90a278f5fab7557e69e97056c0841adf269c42697194f0bd5c5e69152637d4b3","impliedFormat":1},{"version":"69c9a5a9392e8564bd81116e1ed93b13205201fb44cb35a7fde8c9f9e21c4b23","impliedFormat":1},{"version":"5f8fc37f8434691ffac1bfd8fc2634647da2c0e84253ab5d2dd19a7718915b35","impliedFormat":1},{"version":"5981c2340fd8b076cae8efbae818d42c11ffc615994cb060b1cd390795f1be2b","impliedFormat":1},{"version":"f263485c9ca90df9fe7bb3a906db9701997dc6cae86ace1f8106ac8d2f7f677b","impliedFormat":1},{"version":"4c64e7fa79f96cac57f4e22899805f88d22d69ac673b63fe4fa14e5229200bba","affectsGlobalScope":true,"impliedFormat":1},{"version":"0250da3eb85c99624f974e77ef355cdf86f43980251bc371475c2b397ba55bcd","impliedFormat":1},{"version":"f1c93e046fb3d9b7f8249629f4b63dc068dd839b824dd0aa39a5e68476dc9420","impliedFormat":1},{"version":"eab2f3179607acb3d44b2db2a76dd7d621c5039b145dc160a1ee733963f9d2f5","impliedFormat":1},{"version":"12806f9f085598ef930edaf2467a5fa1789a878fba077cd27e85dc5851e11834","impliedFormat":1},{"version":"1dbca38aa4b0db1f4f9e6edacc2780af7e028b733d2a98dd3598cd235ca0c97d","impliedFormat":1},{"version":"a43fe41c33d0a192a0ecaf9b92e87bef3709c9972e6d53c42c49251ccb962d69","impliedFormat":1},{"version":"a177959203c017fad3ecc4f3d96c8757a840957a4959a3ae00dab9d35961ca6c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6fc727ccf9b36e257ff982ea0badeffbfc2c151802f741bddff00c6af3b784cf","impliedFormat":1},{"version":"19143c930aef7ccf248549f3e78992f2f1049118ec5d4622e95025057d8e392b","impliedFormat":1},{"version":"4844a4c9b4b1e812b257676ed8a80b3f3be0e29bf05e742cc2ea9c3c6865e6c6","impliedFormat":1},{"version":"064878a60367e0407c42fb7ba02a2ea4d83257357dc20088e549bd4d89433e9c","impliedFormat":1},{"version":"cca8917838a876e2d7016c9b6af57cbf11fdf903c5fdd8e613fa31840b2957bf","impliedFormat":1},{"version":"d91ae55e4282c22b9c21bc26bd3ef637d3fe132507b10529ae68bf76f5de785b","impliedFormat":1},{"version":"b484ec11ba00e3a2235562a41898d55372ccabe607986c6fa4f4aba72093749f","impliedFormat":1},{"version":"7e8a671604329e178bb479c8f387715ebd40a091fc4a7552a0a75c2f3a21c65c","impliedFormat":1},{"version":"41ef7992c555671a8fe54db302788adefa191ded810a50329b79d20a6772d14c","impliedFormat":1},{"version":"041a7781b9127ab568d2cdcce62c58fdea7c7407f40b8c50045d7866a2727130","impliedFormat":1},{"version":"4c5e90ddbcd177ad3f2ffc909ae217c87820f1e968f6959e4b6ba38a8cec935e","impliedFormat":1},{"version":"b70dd9a44e1ac42f030bb12e7d79117eac7cb74170d72d381a1e7913320af23a","impliedFormat":1},{"version":"55cdbeebe76a1fa18bbd7e7bf73350a2173926bd3085bb050cf5a5397025ee4e","impliedFormat":1},{"version":"2beff543f6e9a9701df88daeee3cdd70a34b4a1c11cb4c734472195a5cb2af54","impliedFormat":1},{"version":"2e07abf27aa06353d46f4448c0bbac73431f6065eef7113128a5cd804d0c384d","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"42bc0e1a903408137c3df2b06dfd7e402cdab5bbfa5fcfb871b22ebfdb30bd0b","impliedFormat":1},{"version":"9894dafe342b976d251aac58e616ac6df8db91fb9d98934ff9dd103e9e82578f","impliedFormat":1},{"version":"413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff","impliedFormat":1},{"version":"182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106","impliedFormat":1},{"version":"2f4e6b4d39426a1b85ecf4bdeb9dddbf4d9b3397d95d8555d46f925c9519ec7d","impliedFormat":1},{"version":"78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584","impliedFormat":1},{"version":"89d5d28d4f57e000b836ac273079be1b75710e28ce14750d081fb420d37e2ca5","impliedFormat":1},{"version":"fd4e24ccff3966390600d7f5d6aa1fed5a512e92ada735ea5fbc933d313ad3d3","impliedFormat":1},{"version":"b7cddfe1aa6b86b5fad3c9ccb30d05b3ccb165aebbf112f48d2d8a5f69dd98b1","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","impliedFormat":1},{"version":"bd2c7ada3dee03653d3f601011d30072194bc3970cd93208f9588fbdc0c69347","impliedFormat":1},{"version":"e480da45d32313e7174b265674da504f075f59ef326852f0c5a5d863b438ae85","impliedFormat":1},{"version":"ad54850f61fcf5d014e11be80d2f46fea9265cfa7e77456da876f7833ef81769","impliedFormat":1},{"version":"6f7c9e8bd2b5b6a080b07080065f94900bd3c7e5ebbd3047bc33fcce2fab1dd8","impliedFormat":1},{"version":"3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","impliedFormat":1},{"version":"da5950ee2a90721df6f3fba45f5d05308f7e4c35835392215dd2cd404505e2de","impliedFormat":1},{"version":"ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","impliedFormat":1},{"version":"f42d5fed19610d485c646a0c430e768115567d078c7fc855c57b0c578b3d6cd3","impliedFormat":1},{"version":"ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563","impliedFormat":1},{"version":"d5630f2ad9b4541e5ce891648121022f9412ecdca1820baa1f0104f70fd7eff7","impliedFormat":1},{"version":"4d15375ab13497104bc8fe56fdef2b5fd6853f29255737d23a33fa306ff7fd69","impliedFormat":1},{"version":"2cd3fc1d0d6a1e85baffd2d4f50f5efb192b5446eef567e97c94765402f0aad4","impliedFormat":1},{"version":"e4cbf2f1e89ecccaddd2c045e600ae41b732295953fb06247c7dcbc2d281ed30","impliedFormat":1},{"version":"27bbdb7509a5bb564020321fc5485764d0db3230a10d2336ae5ce2c1d401b0e7","impliedFormat":1},{"version":"8c1697d90c394a6fd955b98eae01238eff628e129b987a68aea10f898a48e7da","impliedFormat":1},{"version":"7580e62139cb2b44a0270c8d01abcbfcba2819a02514a527342447fa69b34ef1","impliedFormat":1},{"version":"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","impliedFormat":1},{"version":"f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","impliedFormat":1},{"version":"d10d63718e1646c2279e3b33831f82c60e31f622b2b7020f1196409ca4c09242","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0","impliedFormat":1},{"version":"f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"a22dd55aa4d39906252000ab8e8a1b83b195eef7f4274eb51e457c1f11cf6580","impliedFormat":1},{"version":"540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f","impliedFormat":1},{"version":"121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3","impliedFormat":1},{"version":"612d9da66bb046a9c1e2e8d026245ded881fc4b9f98cbfae714415d57ee0ae0b","impliedFormat":1},{"version":"32c2ad9494dad5d11b0564a619fee18f388db6c1e9e2cd3c360b3122549691eb","impliedFormat":1},{"version":"6c301d40aec56a74ec7bd7324e31a728dadf9bfba3e96def02938d3d973534ec","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2","impliedFormat":1},{"version":"493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170","impliedFormat":1},{"version":"aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670","impliedFormat":1},{"version":"acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","impliedFormat":1},{"version":"c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","impliedFormat":1},{"version":"25a5f6fd3a2243c859eddc99ab5fba11d970af2fe7a5df9c32b7668f76f97b01","impliedFormat":1},{"version":"8d207e1f9d2c30d6f77dfa693f3827c3fbf0d89240297e10bdfe1041d433df68","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","impliedFormat":1},{"version":"d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","impliedFormat":1},{"version":"6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","impliedFormat":1},{"version":"0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7","impliedFormat":1},{"version":"2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650","impliedFormat":1},{"version":"a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"273782b8454e78f6a8b30d2cfbf6860499c930595095fcc1689637115f0eddda","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41","affectsGlobalScope":true,"impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e","impliedFormat":1},{"version":"5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2","impliedFormat":1},{"version":"faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf","impliedFormat":1},{"version":"7029e566b8df176f703fb59fd437a38670c7a0e02c58b2d66dfb5b2e2b2defdb","impliedFormat":1},{"version":"7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad","impliedFormat":1},{"version":"d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb","impliedFormat":1},{"version":"e9f147ecca73d9346a4c073432843c159ccbe50bdcb678a78f6da10eae2cecf4","impliedFormat":1},{"version":"de061f7d72bd65c06fc1419f841dfdcb29a8e22fe6fa527d1e6eb20b897d4de0","impliedFormat":1},{"version":"663beafc2446079574570cba86e9b15f986f908ddb1b01274509970126fee945","impliedFormat":1},{"version":"a3102887d5058bf4cb5b37fa6964c09e9527c42053b3b5c642b89878620748de","impliedFormat":1},{"version":"0aaaa1727edd29673d85c9b26d7ca4d54e5407a48586903c51b48b7f7d196f61","impliedFormat":1},{"version":"d35bca0b261bff02635758c48e8ab99c61c420d0dfabbcf467e847171d876b7d","impliedFormat":1},{"version":"3bc12c40d90c342ff88a3d876996c555ed5cbee5fe8c3308a240b321f401ee46","impliedFormat":1},{"version":"ba130768aae855a5477e9e148e5c879548e6e7ccbcc56fd1934c8a18ea5b7569","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"b499af2054a037a162b3b72cd886f48bbf32a3502c865c6e29fac7d2ab3ce0b5","impliedFormat":1},{"version":"b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","impliedFormat":1},{"version":"d87f90d2df7b638204d81d6c57e1f2a8cc9317c45ca331c691c375649aa9255c","impliedFormat":1},{"version":"7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","impliedFormat":1},{"version":"8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","impliedFormat":1},{"version":"4cceef18d7f088e797a463e90b7a9dad10c6bc667724b7686e3e740ae00122be","impliedFormat":1},{"version":"7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d","impliedFormat":1},{"version":"cc1954b539604b1e562319119ac7e888172208b32ca873f9a357a92c826bd046","impliedFormat":1},{"version":"a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","impliedFormat":1},{"version":"771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","impliedFormat":1},{"version":"43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","impliedFormat":1},{"version":"232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","impliedFormat":1},{"version":"bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023","impliedFormat":1},{"version":"706dd95827e7ebaabda91d5db2b755233e0952d98570e9c032b0f066a15c1177","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf","impliedFormat":1},{"version":"990b8fad2327b77e6920cc792af320e8867e68f02ce849b12c0a6ab9a1aebb09","impliedFormat":1},{"version":"5eb8cd1cb0c9143d74a8190b577c522720878c31aef67d866fcd29973f83e955","impliedFormat":1},{"version":"120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"a6805fcafed712aea7759f8bc731014f9d22738c1d6ef9d43b8091d1d48346d5","impliedFormat":1},{"version":"c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","impliedFormat":1},{"version":"4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","impliedFormat":1},{"version":"7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","impliedFormat":1},{"version":"d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8","impliedFormat":1},{"version":"142617b3cdf902b69c6464c9fbd942b60ab3e733ca18c032b19e0f7e2adbefe8","impliedFormat":1},{"version":"0b603555f1881f87256ffd6344d3e3ed6d466c2e701eabf381f28be8c2125892","impliedFormat":1},{"version":"897e4f7662488e3ecc79e743bdd3b78f13bdb69a97851afa5b440c4211e32ea9","impliedFormat":1},{"version":"e2e1c6d3b2d93add5200bd7bc1a8cccb4e446836b2111ece45db8683a2c765de","impliedFormat":1},{"version":"251b03d5cd243854ce870d9a9a39f491faf69898c5d6b5eee28cc7649c57417b","impliedFormat":1},{"version":"27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","impliedFormat":1},{"version":"2c4de79f406d137390608e8c0a44fba2ff8e00bacfcae7c9d1781fef10e9440d","impliedFormat":1},{"version":"07ba23a10465791be5d22deaf5ef7de7658774ddff53721e5ea17fedea1bc721","impliedFormat":1},{"version":"dca8c645c5afeb03b1ecedbf16323f33e7d0afaa6256c8e047e6e38087a97f53","impliedFormat":1},{"version":"775f181bd4a533d6f8b5e55ec1d9f1624559720ae8a70e9432258da26b38d27c","impliedFormat":1},{"version":"796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","impliedFormat":1},{"version":"5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","impliedFormat":1},{"version":"9109a1291dd4b9f1541bea81ee11c247a2ca9e1ea89f87f13aa1811c3c069616","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469","impliedFormat":1},{"version":"cd8ce8d68567f62dd580b3c3c37777ac3f5b81944c7417f5ea83030eab533385","impliedFormat":1},{"version":"e374d1eaa05b7dc38580062942ac8351ce79cbe11f6dbce4946a582a5680582d","impliedFormat":1},{"version":"9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","impliedFormat":1},{"version":"a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870","impliedFormat":1},{"version":"49af4b52f0d4d2304c5f2c6fe5fab3e153e0acc38830d0202821b877c097dd02","impliedFormat":1},{"version":"49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","impliedFormat":1},{"version":"bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","impliedFormat":1},{"version":"92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","impliedFormat":1},{"version":"f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","impliedFormat":1},{"version":"e68b8e5a1df7c1be2bc105141456ecba70215806e1c28bfbc5c12bfce4be6e68","impliedFormat":1},{"version":"511c8f02329808d47d00b859c532ae9115590048b17325a946c74dac48428650","impliedFormat":1},{"version":"57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","impliedFormat":1},{"version":"b5f9e66625783eefcbe3d2da074b2e7ba2066d61ce3fc6ef4f22805ad946cab4","impliedFormat":1},{"version":"e37115962d284b9f7a37c2bdd2add50f88365dde41f5e0ff591ffc48a8ec7575","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","impliedFormat":1},{"version":"f89488602bec98a142072fae7ea5ba99431a569ff580c64b7be39896474799d8","impliedFormat":1},{"version":"bbbc47961f39a57df103cf4ca3bb8f8732b4b6678a18225a0aa76d59c466956c","impliedFormat":1},{"version":"2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","impliedFormat":1},{"version":"2ffb043dc5163458e473b7010859f86e01dc4edffcae0a93d885d028b426a546","impliedFormat":1},{"version":"c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","impliedFormat":1},{"version":"dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","impliedFormat":1},{"version":"b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","impliedFormat":1},{"version":"05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347","impliedFormat":1},{"version":"8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","impliedFormat":1},{"version":"04b7b2e0832dfd3c31e81df3975e8d8fda28e7ff999b0aa2932608a8f6661d5c","impliedFormat":1},{"version":"ca2d34c6ed5cbd3070b8b6f32f42ae54adcc6499c1e4b99f0a5798b3f27cc653","impliedFormat":1},{"version":"9ec68995e66dd6b9dac834bf5ae85fde802714ea2e82151a5d1d53ef01b463ef","impliedFormat":1},{"version":"5c4d626b4902f2ef8a1cc146d761d276cef988016dc674e3b98fbad70e64bc9f","impliedFormat":1},{"version":"fdfaa0aad899524962e2955287b5b991ffe3be50f64e02eb60c933ca44644a94","impliedFormat":1},{"version":"53c972a0f9bc3a4ec70fff7314123ea8cfcf75b3703046f767d2dc1eea87b2fb","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","impliedFormat":1},{"version":"7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","impliedFormat":1},{"version":"d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476","impliedFormat":1},{"version":"413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","impliedFormat":1},{"version":"06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","impliedFormat":1},{"version":"7303b45138d2511035056a5901a1490ebdcbf055cbb1276f8629c5121cbe733e","impliedFormat":1},{"version":"27f874cd5327507eeff699a74567f60c1215b94509f4308633a7b01922471ed2","impliedFormat":1},{"version":"a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","impliedFormat":1},{"version":"2c6cf04bc525caf6546e859e8ef10bfb9573837ec0bc5ec7b53a7b1b8ca72781","impliedFormat":1},{"version":"8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe","impliedFormat":1},{"version":"304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357","impliedFormat":1},{"version":"0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","impliedFormat":1},{"version":"87cc05fe13108f02e12da7e3efd8e360fef78d96a0c9e11408ea1b1b9fb3e03d","impliedFormat":1},{"version":"1abbf67c218d23c2ce76887caac2df6c7dab3d97ba2b65348432b876f510002a","impliedFormat":1},{"version":"1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","impliedFormat":1},{"version":"4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","impliedFormat":1},{"version":"c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b","impliedFormat":1},{"version":"f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","impliedFormat":1},{"version":"1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","impliedFormat":1},{"version":"8bd496cf710d4873d15e4891a5dbf945673e3321ca74cf75187e347fd5ed295e","impliedFormat":1},{"version":"a6dba407fc287f1e25454e75028c91bbc00675f2d1c4e8b3edcc36c08611a486","impliedFormat":1},{"version":"d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","impliedFormat":1},{"version":"e91f7b1344577a02f051b9b471f33044fef8334a76dc9e1de003d17595a5219b","impliedFormat":1},{"version":"c0723195c85e19656d6b5b9fdb81d3f3403c1ae4679e722c6ea058c516b38d12","impliedFormat":1},{"version":"186eea74805194f04e41038fc5eca653788b9dedbab7c2d7d17e10139622dd92","impliedFormat":1},{"version":"71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783","impliedFormat":1},{"version":"cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7","impliedFormat":1},{"version":"1594da19968752a22b2ac48c2d0e60575700e745c577a8a4a676b841238ad5bb","impliedFormat":1},{"version":"e0cee12109e0a10a4c3d6769fcc7644b7c1ea7f52365bea51728f5af29f8a137","impliedFormat":1},{"version":"7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","impliedFormat":1},{"version":"3536968defef8a75514f547ead5e2e9c1e984820290ec9b00c5fdfb6ef786535","impliedFormat":1},{"version":"d83773870080c30a230e322ce13a9c6f3398e8dacea4ea8a83e26370f3bac23e","impliedFormat":1},{"version":"dcfeaf98d66314fec29a9076c4290e45d0b196a65827becc19138e9c7b855f37","impliedFormat":1},{"version":"6849fe9210fe4946d5f085bfed36758f33dc6ae15a751338d178dd4daa017c46","impliedFormat":1},{"version":"888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7","impliedFormat":1},{"version":"60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef","impliedFormat":1},{"version":"ffae4e1e06aa848a1e4bcef162cd1c48e5909b26223515981310af9c036bdfc7","impliedFormat":1},{"version":"a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","impliedFormat":1},{"version":"34e16eb7c31768a11a08aebcfb3d70d7b8f0b016197e98d8419e566ceae6d6c8","impliedFormat":1},{"version":"f94ec1f7e4b709d26960306c9082a7a1b728a6e13089346aa48ba57c74cbf47e","impliedFormat":1},{"version":"9a11cb4033405e96c247cd5aa29790212aaffdd127869e8a5219103f0b389fd5","impliedFormat":1},{"version":"01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c","impliedFormat":1},{"version":"aff5213585cb72e94054dfe17250ff315f3569b3919d1ef1ad235f37c4ee894e","impliedFormat":1},{"version":"fb2ea35e1be6388d722d7725e2b49c697d34d9c890c3b96758faaeb86d35cef8","impliedFormat":1},{"version":"ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","impliedFormat":1},{"version":"1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40","impliedFormat":1},{"version":"f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c","impliedFormat":1},{"version":"5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0","impliedFormat":1},{"version":"72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","impliedFormat":1},{"version":"456006a6975b26c0a1785feddae165f6d307e2d601ffde27e21fc4a790e448a4","impliedFormat":1},{"version":"c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","impliedFormat":1},{"version":"ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","impliedFormat":1},{"version":"1fe0d18b111e1145a7e7601855bccd4ca20f24e3b9a5aba6bb1fa9d1a7059170","impliedFormat":1},{"version":"5632c3c26d420c063eebe64c45b1248b9492a67bf44f1d0c57e9dc8f6cf449bb","impliedFormat":1},{"version":"0df5aa619ab12993a39ea6dae062ee46eadbb4d738916460e636ada52bced75b","impliedFormat":1},{"version":"8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345","impliedFormat":1},{"version":"35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","impliedFormat":1},{"version":"10ab7be91f87ebe8916b62cf28af2e45b5601fc7b0e311adf838f912c6b31dd8","impliedFormat":1},{"version":"bc636fbc08e0979ceb7eb0731a33000283d77a33b62e1f71ee65be50394e40ba","impliedFormat":1},{"version":"7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","impliedFormat":1},{"version":"045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"75bbd3be047d539988a0ff0b56384ef7a6a25f3b676ad96bee547d44c31622a7","impliedFormat":1},{"version":"42960001a776b089ade681ab5cfddc936e0afb0615133ec1841f3dee89d3e1bf","impliedFormat":1},{"version":"0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f","impliedFormat":1},{"version":"da47712b394d944328245482603bc6f416d3949b67c9392279caab595076b510","affectsGlobalScope":true,"impliedFormat":1},{"version":"37d0071d8f0a06dc55c2c5e0ec3391affd4fd107c53410bf358196ec0bf3923f","impliedFormat":1},{"version":"b213dad76ca37fd552274c9499056e1c0d9c1bd38a55bb7f68b22ba6b84c3ad7","impliedFormat":1},{"version":"56ccb49443bfb72e5952f7012f0de1a8679f9f75fc93a5c1ac0bafb28725fc5f","impliedFormat":1},{"version":"20fa37b636fdcc1746ea0738f733d0aed17890d1cd7cb1b2f37010222c23f13e","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"bc03c3c352f689e38c0ddd50c39b1e65d59273991bfc8858a9e3c0ebb79c023b","impliedFormat":1},{"version":"19df3488557c2fc9b4d8f0bac0fd20fb59aa19dec67c81f93813951a81a867f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"b25350193e103ae90423c5418ddb0ad1168dc9c393c9295ef34980b990030617","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","impliedFormat":1},{"version":"5a49adaef698b7ad7e6127949fa1b0bbd3d46b7cbd11c54e392a4dcdd51f5190","impliedFormat":1},{"version":"96171c03c2e7f314d66d38acd581f9667439845865b7f85da8df598ff9617476","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","impliedFormat":1},{"version":"2489bf04d77dc025ba67f49f1a56eb24b9db477d5ff88123d887e163ed1776aa","impliedFormat":1},{"version":"63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","impliedFormat":1},{"version":"4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","impliedFormat":1},{"version":"0b77b819b5417775fccb20c678293cf614c054a5b1a65421a5b933a9124ba998","impliedFormat":1},{"version":"e1f6076688a95bd82deaac740fccbe3cdea0d8a22057cccc9c5bce4398bdd33b","impliedFormat":1},{"version":"9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","impliedFormat":1},{"version":"b1f1d57fde8247599731b24a733395c880a6561ec0c882efaaf20d7df968c5af","impliedFormat":1},{"version":"d7c1bbcddb06dcc8c9184013ace33c0dc71af715ab5987ccb42b903d2ec91193","impliedFormat":1},{"version":"35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","impliedFormat":1},{"version":"1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","impliedFormat":1},{"version":"5a3ea721d03a361ccbdd7390ccd75f6e84cbca3a3f01f4b331ecc9af31890c49","impliedFormat":1},{"version":"e7dfaee4af38d45b1cab8a1ee0b3bc1f85ddcf64545ed391d675d78ae6526274","affectsGlobalScope":true,"impliedFormat":1},{"version":"98e2b197bf7fe7800f89c87825e2556d66474869845e97ad9c2b36f347c43539","impliedFormat":1},{"version":"af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","impliedFormat":1},{"version":"332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93","impliedFormat":1},{"version":"94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","impliedFormat":1},{"version":"07ed3ddab975995eea41b22f3010506fb9f5fb301d04820b07d7a1aee5477d7c","impliedFormat":1},{"version":"969d8b0965849f4bae7cab0ba90bd1e1220e95999c2c6f01117fa7500901c017","impliedFormat":1},{"version":"6ec840ee5e2bc103f557fe38b1d585ee250540468713d7634ee066de372bf332","impliedFormat":1},{"version":"b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20","impliedFormat":1},{"version":"1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a","impliedFormat":1},{"version":"e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8","impliedFormat":1},{"version":"1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","impliedFormat":1},{"version":"ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca","impliedFormat":1},{"version":"5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"e297c0a524edee7677939122f90027bfbe5f2698939d9a85728e5044b39c7124","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","impliedFormat":1},{"version":"b62381cae176db34f003cc6172ee8f3e0122014889d66391aa73698105cf4934","impliedFormat":1},{"version":"1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","impliedFormat":1},{"version":"84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","impliedFormat":1},{"version":"1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","impliedFormat":1},{"version":"30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","impliedFormat":1},{"version":"03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","impliedFormat":1},{"version":"41eb514d9ce0a6e87957f08a4b7af70d93f87637f37dee706e2d92a6601c25a9","impliedFormat":1},{"version":"e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"7bd01f0f28cd3aeb2046274d85208e245965f6f2948edf4f7b2057bcf9f22ccc","impliedFormat":99},{"version":"d2f2cf2b8cc92bea913cda4a076e0f790b23a21e84f989d12f0116a7fe3906e0","impliedFormat":99},{"version":"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5b20bc288ee49989c95b20847fc93b96bf61cc0845598897a6a53a967dd7d07","affectsGlobalScope":true,"impliedFormat":1},{"version":"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","impliedFormat":1},{"version":"3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","impliedFormat":1},{"version":"d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},{"version":"2d938bf4079693698be6e8b8a95e19f1a29e260424d16dcd66e66f25c386f136","affectsGlobalScope":true},"7b550dda9686c16f36a17bf9051d5dbf31e98555b30d114ac49fc49a1e712651",{"version":"eeda3df3e4de7b1ff3a5e7b72972b65b4eefbb90e33bb38031a0623ae80c67f5","signature":"435a1e418e8338be3f39614b96b81a9aa2700bc8c27bc6b98f064ff9ce17c363"},{"version":"063f3e0ea960cd59fef476d86df780c8ebc6fe1ba0de825835418a7359e5643b","signature":"e3ccebf97151a5c62166d8622ac88432419b71195c1cdd6b6e4501a0f11eade2"},{"version":"66eba7675e8c2bc037674b2b7578df051a393a3c220231b8fcc43378c66964bc","signature":"43edb33a69e6920d9fe7016657e07a6f4e130cb3e46a97eeed807bc3dd9f6f6a"},{"version":"d459ce316d5c6ae530a0ff051dfeb16ff521320a5f15c4fd9a34fefe7fcb800d","signature":"c95dacfb4db88847aa346c9d01facb524454833e0134b067261d1e62b6196e94"},{"version":"c57b441e0c0a9cbdfa7d850dae1f8a387d6f81cbffbc3cd0465d530084c2417d","impliedFormat":99},{"version":"8658354b90861a76abc7b3c04ece2124295c7da0cc4c4d31c2c78d8607188d03","impliedFormat":1},{"version":"5caf3e99d06350b0b68254c26285e49505f27ddd1c30e6e76433abba8d8b0468","signature":"585cfd5b16667f9cb62d85be5901768ba03ff901e61800f870b8ce0ec513e6d5"},{"version":"f1d832c23ce8d4828d2cac0ca4a126edf434535795f04a3d0986761935f83490","signature":"47d397e45eed5ca4cc509f3a0de48f41d2de57adf947a5f86bb83d4781b05eef"},{"version":"5eddf558b266bf1745ecccfeab30557b28174a2f502695f21e4404435e4039c6","signature":"607a7b8e8a2c331b91e7ab65869ee38493a84e7a0948e369768938a850b9f4a3"},{"version":"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","impliedFormat":1},{"version":"3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","impliedFormat":1},{"version":"1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7","impliedFormat":1},{"version":"079d8ef7984bf9a35e7d6269ece4847cfdbb88092b2d9c2cb2460706c7c263f9","signature":"bed3fd7c26cafcf037171a49bfceb4fd1cbdb654019f26e611f54b6dc8359a4c"},{"version":"9248fa06779f02429f8bf9b6f321acf8511c8b47f71ef7a518379c15b69a6905","signature":"cb652e1e62c4d0eeb4dd3eea31aa01ad5924166877630b9f0bfceaffce49928c"},{"version":"a346701ad6dcdaa58e388fe0995fc5304c09c395b8cba68ed872780f8c102004","impliedFormat":99},{"version":"2fbe402f0ee5aa8ab55367f88030f79d46211c0a0f342becaa9f648bf8534e9d","impliedFormat":1},{"version":"b94258ef37e67474ac5522e9c519489a55dcb3d4a8f645e335fc68ea2215fe88","impliedFormat":1},{"version":"f979d8ba0f8648e2ebf960845752173dad6fa140bd1df1bbf874ed1b1f9e1798","signature":"2b7987ac2e41dc2b36a25892a51bbf67840e12e0dbff6c71792aca0ffe671987"},{"version":"a9373d52584b48809ffd61d74f5b3dfd127da846e3c4ee3c415560386df3994b","impliedFormat":99},{"version":"caf4af98bf464ad3e10c46cf7d340556f89197aab0f87f032c7b84eb8ddb24d9","impliedFormat":99},{"version":"7ec047b73f621c526468517fea779fec2007dd05baa880989def59126c98ef79","impliedFormat":99},{"version":"6b5f886fe41e2e767168e491fe6048398ed6439d44e006d9f51cc31265f08978","impliedFormat":99},{"version":"56a87e37f91f5625eb7d5f8394904f3f1e2a90fb08f347161dc94f1ae586bdd0","impliedFormat":99},{"version":"6b863463764ae572b9ada405bf77aac37b5e5089a3ab420d0862e4471051393b","impliedFormat":99},{"version":"904d6ad970b6bd825449480488a73d9b98432357ab38cf8d31ffd651ae376ff5","impliedFormat":99},{"version":"233267a4a036c64aee95f66a0d31e3e0ef048cccc57dd66f9cf87582b38691e4","impliedFormat":99},{"version":"9f8c675ceb6c825e2a9a5e1b39960525c43834b8ca6ad874fbdbf63f1f09cd5a","signature":"fc55c712db00df87b9302b6935a93f2889f9b4d8c28cca6484900a042d5b806f"},{"version":"23874b6d249b9780eb941f5abb1b4c578219dcaaaad8783be2e731f922bdf291","impliedFormat":1},{"version":"e00b4518f09adace0636d24038f05313e862250a22c7be319c5e8031276f651c","signature":"ff53dc24dd9f26eaca109b151c98025c3e47f72426866ab0bcd101e453dbc9dd"},{"version":"76547aa25121404fbf43cb3fb6f45167f5354cb1e334584358e64b2ddcb90093","signature":"200216340101dc6b4e92c1683453e1e46f0c97b89d88900fc9a197075833ce96"},{"version":"8dd450de6d756cee0761f277c6dc58b0b5a66b8c274b980949318b8cad26d712","impliedFormat":99},{"version":"dfcf16e716338e9fe8cf790ac7756f61c85b83b699861df970661e97bf482692","impliedFormat":99},{"version":"07e207898be56c250cc758649f31fa983ddf16e53c2f1d2dcf0314e6a0aba264","signature":"5a44581e840bddd6c9cfde921db71261ee74bed6da3d0861dd42d9531252d9e0"},{"version":"68f2eeb522fd05a86f95586a7dc8782b1039069509a6aa85db25d50d3ed118e1","signature":"30afb74e6b5b597303ffe4d077a05d55c671c3a1bfb4415681e52d6985a11bd9"},{"version":"f5df53a3a00c32f5bde8af34f97a4f67e2a96d0bc2e6c3597ca235720f192bbe","signature":"709272c42559e70eedc6c083e335af589121386451ed38b3f3ff379b196205a9"},{"version":"04a95c05e71185c8acea19d0a6c8096a7daa44224f1347f6a2d4452bb7466a78","signature":"0c6f146bf5402327aa93d97c9e263e92bb63b4d87f2af155416cc7d0490a8224"},{"version":"ad2422f226f35a92baf163eeb1f32c40395d65813e9e36293c02d39edc79eeaa","signature":"ecfd1bee66efd232643d94e93e82ad4b74aa7bc6be51a32385d2380b29adddda"},{"version":"0efdf2b11edb6785950c53670efbb4a5813c6cc085e3231b635d902af3ba9107","signature":"7ab3d9fc2ec0e3e26ee7b37c9de805b038888f8d8bc9c591058e1780be436e94"},{"version":"a9373d52584b48809ffd61d74f5b3dfd127da846e3c4ee3c415560386df3994b","impliedFormat":99},{"version":"caf4af98bf464ad3e10c46cf7d340556f89197aab0f87f032c7b84eb8ddb24d9","impliedFormat":99},{"version":"cbfd5ef0c8fdb4983202252b5f5758a579f4500edc3b9ad413da60cffb5c3564","impliedFormat":99},{"version":"7052382e1e134486b9326e52c6dd9bd4a81bb4813fd57813cdcd8a49aea258ae","signature":"3f0fadd0f2fab094d3cd8d0770b9250ce6ce854d74006a2acc38bb8b669818f7"},{"version":"48ed8467f93ed92f83d41a15a0023f37dd2837732d8897d294569c73809dd9c3","signature":"fbf16361295db594b3d124baea26b5b09f70339867a58bf27be3892a0f0f71a4"},{"version":"21d12db959628ccae31300b1d2076a28ab32fbca0e3a9d8d3451da21f10fdb68","signature":"884df8f2f8bed06ca74dc5cf624a74b35034e9842d7add988e5b9371d7d64807"},{"version":"7793213d3c40de26af1fb2783d35ce692574a94adde9cf6dda6c8d276b316dfb","signature":"0b44c0bd1b83b53f228ea367d3038ece73250e0978fc7e8ed45068ddf562da2c"},{"version":"4b256748fad36233ae8b8f3759b7afdff51d9309ecfda8c6283bb5eb05645e93","signature":"1b21ee4d8e38113b1059e921afb28cd66a1c80358994e121ff1a40d5f2b845e3"},{"version":"b322fc3662eb7c87f5f1931bfa7026de6f64bdb258ef941ef3a05880ad45881a","signature":"3e3090ce55f8f51433b363bbddc0a58ca1504940b3eaefed56c72ea3d302fac4"},{"version":"41766de350c84813ba2a5b332e17fbe9b79db8e121536c5b8872e62fa40c7676","signature":"708ac20315810b3808de5f9f41ee489f09ee68755a749e9a166ff66a03a2235a"},{"version":"cab310fa236ed8be46df5b797708054284af3550234aefa74c151b3498578584","signature":"fd2345800c25f58d28b68590ae32d6aa5e303288ab3f43b1bec58e10f95627d0"},{"version":"b6873e752a9c4947119d974a8371aaf781dbb9e6ef250b4c87e163c2046d00eb","signature":"926b78c97d00b51643e2ab7ac5d20136d7f0a4d0acbc93b4958d46332d4482a0"},{"version":"56208c500dcb5f42be7e18e8cb578f257a1a89b94b3280c506818fed06391805","impliedFormat":1},{"version":"0c94c2e497e1b9bcfda66aea239d5d36cd980d12a6d9d59e66f4be1fa3da5d5a","impliedFormat":1},{"version":"eb9271b3c585ea9dc7b19b906a921bf93f30f22330408ffec6df6a22057f3296","impliedFormat":1},{"version":"aa4a927d0c7239dff845a64e676c71aeed2bbda89a7fb486baab22eb7688ba1d","impliedFormat":1},{"version":"340a990742a00862049b378aaa482b5bb8323d443c799dded51ce711f4f8eb51","impliedFormat":1},{"version":"89eeeebbc612a079c6e7ebe0bde08e06fbc46cfeaebf6157ea3051ed55967b10","impliedFormat":1},{"version":"4c72f66622e266b542fb097f4d1fe88eb858b88b98414a13ef3dd901109e03a1","impliedFormat":1},{"version":"23a933d83f3a8d595b35f3827c5e68239fb4f6eb44e96389269d183fe7ff09ba","impliedFormat":1},{"version":"2acad3ae616a9fb5a8c3d4d7bb5edb11d1d0102372ee939e7fc64359fec4046e","impliedFormat":1},{"version":"c812eabb7d2e13c8e72e216208448f92341a4094dd107cbb0bdb2cb23d1a83e7","impliedFormat":1},{"version":"f734b58ea162765ff4d4a36f671ee06da898921e985a2064510f4925ec1ed062","affectsGlobalScope":true,"impliedFormat":1},{"version":"55c0569d0b70dbc0bb9a811469a1e2a7b8e2bab2d70c013f2e40dfb2d2803d05","impliedFormat":1},{"version":"37f96daaddc2dd96712b2e86f3901f477ac01a5c2539b1bc07fd609d62039ee1","impliedFormat":1},{"version":"9c5c84c449a3d74e417343410ba9f1bd8bfeb32abd16945a1b3d0592ded31bc8","impliedFormat":1},{"version":"a7f09d2aaf994dbfd872eda4f2411d619217b04dbe0916202304e7a3d4b0f5f8","impliedFormat":1},{"version":"a66ebe9a1302d167b34d302dd6719a83697897f3104d255fe02ff65c47c5814e","impliedFormat":99},{"version":"a7f23fecdccf1504dae27c359db676d0a1fbaaeb400b55959078924e4c3a4992","impliedFormat":1},{"version":"bee66a62aa1da254412bb2c3c8c1a0dd12efea0722d35cc6ea7b5fdaa6778fd1","impliedFormat":1},{"version":"05d80364872e31465f8a1eaf2697e4fc418f78aa336f4cea68620a23f1379f6f","impliedFormat":1},{"version":"7345ba3b9eb2182d8cdc4c961b62847c3c9918985179ddefd5ca58a80d8b9e6a","impliedFormat":1},{"version":"81c4a0e6de3d5674ec3a721e04b3eb3244180bda86a22c4185ecac0e3f051cd8","impliedFormat":1},{"version":"39975a01d837394bcac2559639e88ecdc4cfd22433327b46ea6f78eb2c584813","impliedFormat":1},{"version":"7261cabedede09ebfd50e135af40be34f76fb9dbc617e129eaec21b00161ae86","impliedFormat":1},{"version":"ea554794a0d4136c5c6ea8f59ae894c3c0848b17848468a63ed5d3a307e148ae","impliedFormat":1},{"version":"2c378d9368abcd2eba8c29b294d40909845f68557bc0b38117e4f04fc56e5f9c","impliedFormat":1},{"version":"9b048390bcffe88c023a4cd742a720b41d4cd7df83bc9270e6f2339bf38de278","affectsGlobalScope":true,"impliedFormat":1},{"version":"c60b14c297cc569c648ddaea70bc1540903b7f4da416edd46687e88a543515a1","impliedFormat":1},{"version":"acfa00e5599216bcb8c9f3095e5fec4aeddfcc65aabe0eac7e8dbc51e33691c9","impliedFormat":1},{"version":"922d8f0f46dbe9fb80def96f7bcd9d5c1a6c0022d71023afa9eb7b45189d61f2","impliedFormat":1},{"version":"90588fb5ef85f4a8a4234e8062eb97bd3c8114dfb86a0c67f62685969222da8b","impliedFormat":1},{"version":"6ce50ada4bc9d2ad69927dce35cead36da337a618de0a2daaaeeafe38c692597","impliedFormat":1},{"version":"13b8d0a9b0493191f15d11a5452e7c523f811583a983852c1c8539ab2cfdae7c","impliedFormat":1},{"version":"8932771f941e3f8f153a950c65707d0611f30f577256aa59d4b92eda1c3d8f32","impliedFormat":1},{"version":"df6251bd4b5fad52759bfe96e8ab8f2ce625d0b6739b825209b263729a9c321e","impliedFormat":1},{"version":"846068dbe466864be6e2cae9993a4e3ac492a5cb05a36d5ce36e98690fde41f4","impliedFormat":1},{"version":"94c8c60f751015c8f38923e0d1ae32dd4780b572660123fa087b0cf9884a68a8","impliedFormat":1},{"version":"db8747c785df161ef65237bac36a7716168e5ebf18976ab16fd2fff69cf9c6ce","impliedFormat":1},{"version":"3085abdf921a6d225ad037c89eb2ba26a4c3b2c262f842dd3061949d1969b784","impliedFormat":1},{"version":"8e8f7b36675be31c4e9538529c30a552538c42ff866ba59fe70f23ba18479c5a","impliedFormat":1},{"version":"f4f7fbf0e5bf2097ddee2c998cca04b063f6f9cdcb255e728c0e85967119f9e5","impliedFormat":1},{"version":"c5b47653a15ec7c0bde956e77e5ca103ddc180d40eb4b311e4a024ef7c668fb0","impliedFormat":1},{"version":"223709d7c096b4e2bb00390775e43481426c370ac8e270de7e4c36d355fc8bc9","impliedFormat":1},{"version":"0528a80462b04f2f2ad8bee604fe9db235db6a359d1208f370a236e23fc0b1e0","impliedFormat":1},{"version":"17fb3716df78592be07500e9a90bd8c9424dd70c6201226886a8e71b9d2af396","impliedFormat":1},{"version":"82ef7d775e89b200380d8a14dc6af6d985a45868478773d98850ea2449f1be56","impliedFormat":1},{"version":"b86720947f763bbb869c2b183f8e58bca9fa089ed8f9c5a1574b2bea18cfbc02","impliedFormat":1},{"version":"fb7e20b94d23d989fa7c7d20fccebef31c1ef2d3d9ca179cadba6516e4e918ad","impliedFormat":1},{"version":"8326f735a1f0d2b4ad20539cda4e0d2e7c5fc0b534e3c0d503d5ed20a5711009","impliedFormat":1},{"version":"8d720cd4ee809af1d81f4ce88f02168568d5fded574d89875afd8fe7afd9549e","impliedFormat":1},{"version":"df87c2628c5567fd71dc0b765c845b0cbfef61e7c2e56961ac527bfb615ea639","impliedFormat":1},{"version":"659a83f1dd901de4198c9c2aa70e4a46a9bd0c41ce8a42ee26f2dbff5e86b1f3","impliedFormat":1},{"version":"1db5c2491eebd894eb9be03408601cddfe1b08357d021aeb86c3fb6c329a7843","impliedFormat":1},{"version":"224f85b48786de61fb0b018fbea89620ebec6289179daa78ed33c0f83014fc75","impliedFormat":1},{"version":"05fbfcb5c5c247a8b8a1d97dd8557c78ead2fff524f0b6380b4ac9d3e35249fb","impliedFormat":1},{"version":"322f70408b4e1f550ecc411869707764d8b28da3608e4422587630b366daf9de","impliedFormat":1},{"version":"acb93abc527fa52eb2adc5602a7c3c0949861f8e4317a187bb5c3372f872eff4","impliedFormat":1},{"version":"c4ef9e9e0fcb14b52c97ce847fb26a446b7d668d9db98a7de915a22c46f44c37","impliedFormat":1},{"version":"0e447b14e81b5b3e5d83cbea58b734850f78fb883f810e46d3dedba1a5124658","impliedFormat":1},{"version":"045f36d3a830b5ae1b7586492e1a2368d0e4b4209fa656f529fd6f6bb9ac7ced","impliedFormat":1},{"version":"929939785efdef0b6781b7d3a7098238ea3af41be010f18d6627fd061b6c9edf","impliedFormat":1},{"version":"fca68ac3b92725dbf3dac3f9fbc80775b66d2a9c642e75595a4a11a2095b3c9a","impliedFormat":1},{"version":"245d13141d7f9ec6edd36b14844b247e0680950c1c3289774d431cbbd47e714e","impliedFormat":1},{"version":"4326dc453ff5bf36ad778e93b7021cdd9abcfc4efe75a5c04032324f404af558","impliedFormat":1},{"version":"27b47fbd2f2d0d3cd44b8c7231c800f8528949cc56f421093e2b829d6976f173","impliedFormat":1},{"version":"0795a213434963328e8b60e65a9d03a88efc138ae171bbcca39d9000c040e7a4","impliedFormat":1},{"version":"fc745bebefc96e2a518a2d559af6850626cada22a75f794fd40a17aae11e2d54","impliedFormat":1},{"version":"2b0fe9ba00d0d593fb475d4204214a0f604ad8a56f22a5f05c378b52205ef36b","impliedFormat":1},{"version":"3d94a259051acf8acd2108cee57ad58fee7f7b278de76a7a5746f0656eecbff6","impliedFormat":1},{"version":"46097d076be332463ea64865c41d232865614cf358a11af75095dd9cef2871cc","impliedFormat":1},{"version":"6e18a70a7c64e6fe578a8f3ecc1dd562cd0bf6843bbf8e65fde37cf63b9a8ea8","impliedFormat":1},{"version":"3f3526aea8d29f0c53f8fb99201c770c87c357b5e87349aca8494bfd0c145c26","impliedFormat":1},{"version":"6ee92d844e5a1c0eb562d110676a3a17f00d2cd2ea2aaaff0a98d7881b9a4041","impliedFormat":1},{"version":"b9dc36d1f7c5c2350feafb55c090127104e59b7d2a20729b286dab00d70e283d","impliedFormat":1},{"version":"45d3f1d53fa99783a5e3c29debb065d6060d0db650a6a1055308a8619bd6b263","impliedFormat":1},{"version":"a14febaf38fd75a88620a0808732cf9841afc403da2dc3de7a6fc9a49d36bdbc","impliedFormat":1},{"version":"6052522a593f094cfee0e99c76312a229cf2d49ac2e75095af83813ec9f4b109","impliedFormat":1},{"version":"a0ceb6ce93981581494bae078b971b17e36b67502a36a056966940377517091d","impliedFormat":1},{"version":"a63ce903dd08c662702e33700a3d28ca66ed21ac0591e1dbf4a0b309ae80e690","impliedFormat":1},{"version":"2b63d2725550866e0f2b56b2394ce001ebf1145cb4b04dc9daa29d73867b878c","impliedFormat":1},{"version":"e885933b92f26fa3204403999eddc61651cd3109faf8bffa4f6b6e558b0ab2fa","impliedFormat":1},{"version":"bd834465d4395ac3d8d55e94bf2a39c1f5e9be719c99340957b3b6a3a85ec66a","impliedFormat":1},{"version":"fca1059bad0f439021325957b33c933bca31475e4a3a36dda02140f47ffaf8ed","impliedFormat":1},{"version":"6e2d2b63c278fd1c8dd54da2328622c964f50afa62978ed1a73ccd85e99a4fc7","impliedFormat":1},{"version":"e151e41c82004cf09b7ea863f591348c9035e0f7a69d4189cbac89cc9611b89d","impliedFormat":1},{"version":"0778cfe0d671f153a9d30655b81d5721dc7af6ebe4b654c57417b7cba3649b1c","impliedFormat":1},{"version":"b83ffe71adbac91c5596133251e5ec0c9e6664017ee5b776841effe93de8f466","impliedFormat":1},{"version":"61ecf051972c69e7c992bab9cf74c511ecba51b273c4e1590574d97a542bd4ea","impliedFormat":1},{"version":"068f5afbae92a20a5fcd9cfce76f7b90de2c59a952396b5da225b61f95a1d60a","impliedFormat":1},{"version":"bdf5e07a22e661de2c7115e8364b98ef399c24c9fe62035dc1ac945a9dd3372a","impliedFormat":1},{"version":"4e024e2530feda4719448af6bdd0c0c7cfa28d1a4887900f4886bec70cd48fea","impliedFormat":1},{"version":"99c88ea4f93e883d10c04961dbf37c403c4f3c8444948b86effec0bf52176d0e","impliedFormat":1},{"version":"e88f3729fcc3d38d2a1b3cdcbd773d13d72ea3bdf4d0c0c784818e3bfbe7998d","impliedFormat":1},{"version":"f25b1264b694a647593b0a9a044a267098aaf249d646981a7f0503b8bb185352","impliedFormat":1},{"version":"964d0862660f8e46675c83793f42ab2af336f3d6106dee966a4053d5dc433063","impliedFormat":1},{"version":"292ad4203c181f33beb9eb8fe7c6aaae29f62163793278a7ffc2fcc0d0dbed19","impliedFormat":1},{"version":"aa8e5ac3f73eede931d5da74ef1797c174b00854ac701ead5c4a7d6ce4a49029","impliedFormat":1},{"version":"f1a4ca3688d951daa2d7740da5a0827fa34d4a7709eed7b8225215986ee87108","impliedFormat":1},{"version":"08e159b5ef9d14bdd329457c5cbe181e84f13c4ff2546a24b9eb9129b0c71c46","impliedFormat":1},{"version":"f8453a3fe0fe49ab718357120bec2b8205e15eb91ff62eada60a4780458fa91e","impliedFormat":1},{"version":"06f186bb9a6408ef8563dbf17d53cbe23e68422518b49b96afac732844ddbaa1","impliedFormat":1},{"version":"525f9c06245b5b43b1237cfd757396fd7fd8090e5d6a4ded758c7ce17a04bf42","impliedFormat":1},{"version":"04bc74b8fa987f140989e9f4d6dc37f04a307417af3e0a3767baa1eef4964e10","impliedFormat":1},{"version":"6a9d3aa58228faa62ec3d9e305f472a24441f22a8d028234577beb592ec295b2","impliedFormat":1},{"version":"683e2d454f64394931d233740b762dabc379e3ce5c4c4ad4747cdbd6d5fd8e8d","impliedFormat":1},{"version":"18594ddc7900f3e477645819bce4d824989ad296e3d70bdcdce13cabc5d97335","impliedFormat":1},{"version":"9376cce4d849f1d6ad2cb0048807c77cfeb78cee6e29b61dcfe74c7ab2980e18","impliedFormat":1},{"version":"2698935791615907eb632186119dfc307363d6a163f26017084009e44ea261f2","impliedFormat":1},{"version":"4edfc4848068bf58016856dfeb27341c15679884575e1a501e2389a1fea5c579","impliedFormat":1},{"version":"0c3d7a094ef401b3c36c8e3d88382a7e7a8b1e4f702769eba861d03db559876b","impliedFormat":1},{"version":"d3c3280f081f28e846239d27c2f77a41417e6a19f39267d20a282fd07ef36b96","impliedFormat":1},{"version":"7e3a4800683a39375bc99f0d53b21328b0a0377ab7cbb732c564ca7ca04d9b37","impliedFormat":1},{"version":"c777b498a93261d6caa5dbd1187090b79f0263a03526c64ea4f844a679e8299e","impliedFormat":1},{"version":"b4677e9d8802a82455a0f03a211b85f5d4b04cfbc89fc9aa691695b8e70df326","impliedFormat":1},{"version":"7cb0d946957daea11f78a31b85de435e00bcd8964eba66d3e8056ba9d14b9c55","impliedFormat":1},{"version":"b3e441cdb9d9e55e6e120052fe8bf2a8b5e5a46287f21d5bc39561594574e1a9","impliedFormat":1},{"version":"0870e8eb0527c044e844a1d83127f020aa7f79048218a62b2875e818355f8cb2","impliedFormat":1},{"version":"6b7446f89f9e5d47835117416e6d7656bac2bf700513d330254ae979260ce99f","impliedFormat":1},{"version":"9750752db342b88df1b860958a20fac9fd6a507f67c5cfb6bd5cfa8759338b1e","impliedFormat":1},{"version":"946de511c5e04659d9dfaf5ef83770122846d26d3ffe30e636d3339482bbf35a","impliedFormat":1},{"version":"fbcc201a8fc377a92714567491e3f81e204750b612d51a1720af452f1a254760","impliedFormat":1},{"version":"6dd704b0ba0131eb9e707aeedc39be6a224b4669544e518217a75eb7f5dd65c2","impliedFormat":1},{"version":"6effa89f483e5c83c0e0063df5f1d8b006d9d0f1de7eed2233886642424dc8fb","impliedFormat":1},{"version":"84a8c844f9562da8994c07b44dd2777178a147e06020c62a7f6e349e695e7149","impliedFormat":1},{"version":"d43130c35762a80da2299f8b59a4321b6e64acfb0b11a36183379b4c7b83314b","impliedFormat":1},{"version":"6bf44b890824799af8e20c0387ffa987e890fac5c5954a3a7352351eefe55d5d","impliedFormat":1},{"version":"892b19153694b7a3c9a69bcedb54e1c8ad3b9fa370076db4d3522838afd2cd60","impliedFormat":1},{"version":"5461fca70947a4d8fa272d3dda4c729317cec825141313352adf33bc94de142a","impliedFormat":1},{"version":"f83afa274e0f11860c6609198ecca220f5df60690923b990ca06cae21771016e","impliedFormat":1},{"version":"af31f37264ea5d5349eec50786ceca75c572ed3be91bdd7cb428fdd8cd14b17c","impliedFormat":1},{"version":"85e4673ec8507aef18afd4a9acfae0294bdfaac29458ede0b8b56f5a63738486","impliedFormat":1},{"version":"40683566071340b03c74d0a4ffa84d49fedb181a691ce04c97e11b231a7deee4","impliedFormat":1},{"version":"81c8ab81daa2286241ad27468d6fc7ad3ecc62da04b18b77ce9b9b437f6b0863","impliedFormat":1},{"version":"f158721f7427976b5510660c8e53389d5033c915496c028558c66caaf3d1db1c","impliedFormat":1},{"version":"8e56db8febfe127a9142435940c9a5a1ad17ddb2b2a6d8e9e8984785a76db1fd","impliedFormat":1},{"version":"6113c2f172a875db117357f0aa35aa7c1b6316516e813977ef98dc3b4b8baf2a","impliedFormat":1},{"version":"f25c9802b1316afbf667dd8fa6db4ed23aa5e7acc076a1054ca45d7bc9c8e811","impliedFormat":1},{"version":"e99285f74c22ad823c0b9fac55316b84144e15eb91830034badd9eb0fafe71bf","impliedFormat":1},{"version":"8d696ed3ac9b4b35dca2236dd8ff66d9c9d23a3c4dce2a3fd1a83ff9987c5026","signature":"d397165d10bc0284298d2899ed1f9edc2968d3ac34bf5289c3881800e750fa47"},{"version":"691576ad86911c3fcd0a0e265967175ad6f6464dc4dac91ee2824acfbe03ffb2","signature":"1a8400f26b84e5e32151a2b2e85585e7ca885297b88cab0a3fc35b3b3d2bb211"},{"version":"12ad5960906155b5dfe174f6778872a1973f455e2fbe901ea671a846975b4a19","signature":"8325e920cb32f701bc809bc1e30a6a71986ab8c385af5f0bcad77a8829e9729f"},{"version":"9a74a8dd455c029b58a62027d569a8e3d926dcf706496687249444c4decb5811","signature":"4a73763e8e189e092b6da91d3f7c30688a34a1d798da42682f687bf4474fa95c"},{"version":"a4b38cfdcbee30ac1b0b204c182a7d07f1f9aa1ec751f12a7998ae3b9598ee3f","signature":"12bcab3bfb30588e85769ecfd7f9de33934253b073536eb21d061404c16d8d39"},{"version":"953e6880df1b0419332e8a31f21a037c715fd7c71dc515b29b017bca8b44b7c3","signature":"eb8ee6710e25a0b2f6044748f2a1422949f4a2cbdd79f3a74080c46bee072638"},{"version":"95eb417afd144df1c31273245596659ea4e5947b8d23f097cc61279cc723828f","signature":"471fd87d41418655a214056ff755283cd4870524de30494d972ebf46b2cdb574"},{"version":"3d20a0cf9140ed9fe53b19ea69b058d86a6841d9e7e54674ee5ae666402e5467","signature":"8da31acf55cd3aedcb0285267b0823f8295d3cefe8ef377101ccffc4b0070a0a"},{"version":"777546d8bbc3d43cc0ef858041b28804f69bd851a06386ce16c975918fbeb8f8","signature":"0a7bf9093b0cbd935d56c9d074b86105c560daba0381a555f1df9d1b83549b83"},{"version":"15afe4293812a31199c97a0accfe30430b6c70baef94286ea16c4d071f753859","signature":"f669de2b5bf5dd9fcc98237f9facd9f8326ff66ff4ce83c5ac5725bcc58ecba2"},{"version":"95304b772fabc5873d5dd0365f47cc65fd1b8c260435a7e4d70294886d403e7c","signature":"d0abe03e95ca225314119408229d7b9625b908aea43fce0a7e5872f646973c04"},{"version":"fad7a030f2877c1ed2ce907cacad8e065320ba2ef6b61f83cda15770b77d77e5","signature":"76d68cbc00d6925e73af71243e3ccd31c9515efa95d3b7b3cb03d6f5a2b77056"},{"version":"565670944a76574a13ada62e6edf1eb1413d1e6ea3894a3486b24e9ecb10f7d6","signature":"a8a06dc0a1e36aaa4162766fd882da1697eba29434a309e12041483bc423580a"},{"version":"85fec9e5b071aac2ed35418673e1a7e8e3f10148a32419d298cced068fc661a8","signature":"240a58698736075e65a527f11aafe42ccb60cf8c0bb3b8d3a8889245922f85c4"},{"version":"12204429573ba4b61b60484828be75841597a8d7dd978c102a725a8a880440e6","signature":"c71002ea80b28f14fd81a13a8dda05d3a98a8ecb0a64888544ac3bc6a636cf78"},{"version":"605b5aba1272c8f9bc1e672a555cdac1d0d781f2c9cfeb3dd49e1d180db4aa44","signature":"bc8849990213f7f3e95fc4222ef0bd45ffea3b1251ed9a6985a1ef0666f662d9"},{"version":"6c6a12d601355da30c714097f590d72a16aa114e2d72e37f4797b0d31dae0ba5","signature":"bad62e5ea5ef46ebf7704e55ba8e47b60e380ddcdf183d4a09cb162ff2bf3879"},{"version":"22b0fd7033c7f0e92d8a5ca32eb5254d70e3ee931919242706a0d524c381e8ce","signature":"755798ea96eeae323d848de0206751090f595273f1f853b5c1d04b1235ca8b7f"},{"version":"7cc9bf1e4f1864c04056f1ee89716943dce2d4850e9e05eb03ecc32bf7d74fce","signature":"8ee414fa622c8c5cb9a69aba11b37adc0a5e0fb401626f447d56644a50ebe69e"},{"version":"2c708083cffcd5119d040c9b8f8f5fe259550cb17205f9174b02f8617e57b106","signature":"c77a8ce874e0ca2df9a88d5a7c5a452882352d4262fec0434943c8a648c7ffa9"},{"version":"06745bc9a9c3a9f3d97718d4446c52d596b8e9d5826d77a6c95b5f8ff138f8fd","signature":"debfe7fc4dd9f5184d4a716ccffe7d24b072f0e5263d2881952c95ba592ab2ab"},{"version":"77e4ba02048400bf4e64f58b93ff848116eda2aadcca60b8133a08d35ad725ad","signature":"192be80a9f90c617a404399be85e8b6ca74e547edf217e33111a142620c7946e"},{"version":"474b1c988b0c06b9d10a8d6546b066519950238e7bcc28d670ee39d152d48328","signature":"fde025fc2f0267ff1a10b7ba7e5253b1b886f1b4d4690e8a680a4e0d6f08b949"},{"version":"93aaae477660153079c53e921575a580559c2fbd2a59e3ca1b79dddc8f304a31","signature":"26ba71f79eb1ccc526399c703ceb9595712a793b7d939584ca92df4a3ce4fea8"},{"version":"48f32b2b3e943f8e6d02b2e30a93c4efa13c0527555a83e702191048947e317a","signature":"b0993bdddd0ce31d71d7beea0992f9b070dd48906c7457df9d812dbefd6f0f40"},{"version":"91686efa46d09d983039c195996a1dc1cb631ee00329cd76c4cd4f2d91d0cc03","signature":"48a46c99b85b9ba60bd5a5c694fac0481348ea4dc9b4629e4b4679d315f59305"},{"version":"ed11b17c09b281301058230fc8e4a1c273cd684f0d70a6dbc23c7085efec5313","signature":"14015613b6b5d535af589c95b5d2b0533b6db9e709fa964c5324b48ec6ffc1c8"},{"version":"e829543ecc4b30d9e50716032d0c2c1553ae1cb3e7fe2233407685f6b61b5ffe","signature":"ef38fc7498f6f123b36fcb4a4c22f9df96c764ea263417be343d43376416ff73"},{"version":"5e31268677d4ae42dbd6190820d43b07f758fbed7c5d016ba5c53447c538d442","signature":"6accc96342df1f06d087e46297929a587ed18f6df2b256f848f378f8bed7c6d3"},{"version":"3b55531f1a35394118edfa3fbdd4054cf546d15e32d40e2b7510452336571e7e","signature":"8c06cbb3f494b28541de750957831a13b5b462a6cc6c0f4297cb404fc30d50d1"},{"version":"ca10f4d2a213b1dfc486dbe03d5273ca5e04cfd157ce944adf518e2d26dd03da","signature":"afa678d89fcbd417ff3ae58ed361134899bd25ad56fd1c4cb8a47feacb9faad8"},{"version":"abcf8cbe1969a16024c253392a297cd4701a7f5969ab52c96dc3d001f548e7ec","signature":"d96fba81e02f521232dfc699b69d254028299ecc681a0df5be42e23f8c2448d4"},{"version":"c62cb3242a787917329ddc371f1bf51f568926c90fd2ba7421ae670d9267c6b8","signature":"ebfca8b0c9b7cadac95d6b698580a178f657bbff6b995f548191398b4ed28cd1"},{"version":"f2830e4fddc28c2d9d122d344bd10e32ff30ef56e5ffaeef3021f2e84cbf2fdd","signature":"a5f93244143e79a866d142d5de5d8299abd7bbbf0115e32210c9b744363b3c35"},{"version":"eb33752cf6432a0ad3f5d7846102c21e2f457f20c5ece1cd63ed45290599a216","signature":"c82a115863c4cde821b3f1b6bc316b155d1207cbfe057207c9d2d5fca214b39b"},{"version":"8c30dafedf7ba2e393f7d1134c6f454a6cc7553acc8fbf16854f9927e13aad46","signature":"eb7e43e8e1fe2d5f070198f20edb0b3b6c307fc0b7c2e7cbcf62d3f8de830a7c"},{"version":"664d4a8a607523810529a009732e1b10f46e2688083ae5819913efdbf73ac846","signature":"349147de1de40803fb3f9038fe1c2b1597f4b3dd6a99a6fe45cc844d97c0f39e"},{"version":"7dd644c35def0fab97f47e2d75a98dd882868b921003b0886381dcfc26d8d20b","signature":"79bf53c544df46f140cd1e081a99600a6903127d8d997859f66b0bddab3dd0b1"},{"version":"1cc938ec9a0bd4117a3ee7d3aa3e06262c0930c2f2396e2a73da71f26282b518","signature":"73522cd0f60ca6e448fea1e136bbccb79c61df337b6fd39bde0959fbb74e810a"},{"version":"1179ef8174e0e4a09d35576199df04803b1db17c0fb35b9326442884bc0b0cce","impliedFormat":99},{"version":"a029874021b1db3f6cd3b0402c958d3d194fccf456c4a591cce9abf2ea639156","signature":"3c3b79cf8d289912b0ee1489262591ebb1814777f8045b37da215e2df966946c"},{"version":"ebe878d096aa53f9e9ff8609a7f84c0d9ef415dbfe4b2c80e048960beb571f8c","signature":"ffe34a98472d6797a2ff19c9ccf94fc8ab3f5e46b776ffee9428c1db04f72da7"},{"version":"6c6de25ac4d301478fbf554c672d61fef069352c43709f8e62f2d1b971e84032","signature":"857913035786a76bf02c73cb8451dc7f4561b30bdf05ca2bbba7ae8bf009d528"},{"version":"8a0f57d2cc410ae82e840dea5fbbbbae5fca6e2265298efb70d808f0d7c07f9e","signature":"c1f4f7e88b86ebd7309c3e5e57dbcedf00822943aa3732c40698ccc56f8244f3"},{"version":"de075a8d91636d2a2aea19e7830b3fd4b5d6471580743a70a20e3ee4039a3830","signature":"445a07d734a93a5292b5d1c072e5d24bfda7bceed271a4c4de7eef557e1118dc"},{"version":"66e512fe4477fb8a8fff9236a9b934ae9956397ae85aa36128a6a11a6324a020","signature":"183b0b515ae6a577d385adbc4450d3220beaa7e26455e6fddb240e23d1494d04"},{"version":"28509275b9048aa5b1e68adf3959287dc6c611f2ca85d112b8597f4656b9d121","signature":"408e9487940751a008647b7d4e0f4bf7917bd5c6e6ecca525bd34b7f3bbb849c"},{"version":"653022d4bacac06805de57212dc52a6309d0ab000bbc93b8f4580a9f56ac3916","signature":"9f661d872e9468f78a85ee810648200431bf8aeb710ec786c64b68f39c566de6"},{"version":"8dbcba3fa7ccc963b4cc6646163eb8dc9be07a28defa72c15467411ecc6f3089","signature":"d62108618aab3b20dc7f696c260305f38c11bf7e54f0dff13ec133428fd503a2"},{"version":"ab380f1059d0a43a26a094de13a0039dea78c6d3a13f80a971bab553cba4101a","signature":"d3a64b7a5838e22b1deb8b9f22a56472f97b23b94c2bc6265b7b5bd097777298"},{"version":"fa9b9d0eba206d71c0ea6ffa7f964bf7c83cde0a2a365f1d6bb2349e07ac38d7","signature":"f0246cff9edd8ab5af5accc2412b332dd8cb36180155e48b9d1342e496f95944"},{"version":"47612428a05b0fcd0d7d97327527e5e42aafa0768d2500364682136db3b90bd6","signature":"f626d42ee98f50294dd650d9baf5db6c1f85774cb190959013ec6a40ef25a63e"},{"version":"b98b3fe57a4a737640b3b11f70e2b387b3b820697c786741fabb3f1734c1fa22","signature":"9faea3e3a1a54bac69d3e2ce3a484aef0de09bc94f5760f61703d42b72dc8b51"},{"version":"f8f3ba1c9b1869eb8dc12732fe69e475531abdf6ba19dd56879d3492e37a2780","signature":"7ec3087dd3ccda30258026505c690a36b86478dfd945f6924bc3009b724492d6"},{"version":"27946aa3454d56ff81c37b2afaae796bdfdcf27eebeb8151c34c87d20e5906eb","signature":"d693590cccb013e79d2f8f2f19e004071b301f510d1b46ca06ffe157d4735579"},{"version":"6be7539cbd19db7e03de8a99a57bc967a450da68c7f580c7c39754b8bae81094","signature":"ef66368e447973d9b4390b234f6bbca01d0c01f515233a86c82d6c2e78a614b7"},{"version":"68b6a7501a56babd7bcd840e0d638ee7ec582f1e70b3c36ebf32e5e5836913c8","impliedFormat":99},{"version":"7a14bf21ae8a29d64c42173c08f026928daf418bed1b97b37ac4bb2aa197b89b","impliedFormat":99},{"version":"eb5bd1bac5c6bf4d41c488a44a899e2596de2fc386f8e4bdc11a49fd566ca4ac","signature":"eb7569396fa4507aa7a9c288ea9065bae3df13ff8f9022f3230ad2e6b1c631f9"},{"version":"0e6e31d18b676f3533d374a50f0be7e7af181727de104562342df6bc75660c5f","signature":"e98bfd1f134b3aaa47261dbd6386c6b8d67c33938619f64b8e76a13d3c04e079"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7",{"version":"f76f1afe672c7802f61426f97b19b16bf04aa8dfbfe48d0f68038d39f6dc1b5c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7",{"version":"5dcf7794363ae1240251accc82d64f34675e7d5f9b328a8476f63918597d8925","affectsGlobalScope":true},{"version":"dea0a743fbdf1398485a551fe9016f128911f9d6994eb6eba7c9d4c855d24e25","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"}],"root":[[559,564],[567,569],573,574,578,587,589,590,[593,598],[602,610],[748,786],[788,803],[806,812]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":4},"referencedMap":[[810,1],[811,2],[812,3],[808,4],[559,2],[809,5],[560,6],[561,7],[403,2],[582,8],[579,9],[592,10],[581,8],[591,8],[584,11],[585,8],[580,9],[601,12],[599,9],[600,9],[804,13],[787,14],[575,15],[805,16],[586,17],[583,2],[626,18],[625,19],[622,2],[635,2],[612,20],[636,21],[611,2],[161,22],[162,22],[163,23],[101,24],[164,25],[165,26],[166,27],[99,2],[167,28],[168,29],[169,30],[170,31],[171,32],[172,33],[173,33],[174,34],[175,35],[176,36],[177,37],[102,2],[100,2],[178,38],[179,39],[180,40],[220,41],[181,42],[182,43],[183,42],[184,44],[185,45],[186,46],[187,47],[188,47],[189,47],[190,48],[191,49],[192,50],[193,51],[194,52],[195,53],[196,53],[197,54],[198,2],[199,2],[200,55],[201,56],[202,55],[203,57],[204,58],[205,59],[206,60],[207,61],[208,62],[209,63],[210,64],[211,65],[212,66],[213,67],[214,68],[215,69],[216,70],[217,71],[103,42],[104,2],[105,72],[106,73],[107,2],[108,74],[109,2],[152,75],[153,76],[154,77],[155,77],[156,78],[157,2],[158,25],[159,79],[160,76],[218,80],[219,81],[224,82],[488,9],[225,83],[223,84],[490,85],[489,86],[221,87],[486,2],[222,88],[90,2],[92,89],[485,9],[255,9],[577,90],[576,91],[565,2],[91,2],[741,2],[666,2],[588,9],[511,92],[516,93],[523,94],[506,95],[259,2],[267,96],[407,97],[410,98],[382,2],[395,99],[402,100],[284,2],[384,2],[265,2],[381,101],[427,102],[266,2],[257,103],[409,104],[411,105],[412,106],[483,107],[376,108],[329,109],[389,110],[390,111],[388,112],[387,2],[383,113],[408,114],[268,115],[453,2],[454,116],[295,117],[269,118],[296,117],[332,117],[235,117],[405,119],[404,2],[394,120],[501,2],[244,2],[522,121],[461,122],[462,123],[458,124],[540,2],[359,2],[463,125],[459,126],[545,127],[544,128],[539,2],[310,2],[362,129],[361,2],[538,130],[460,9],[315,131],[322,132],[324,133],[314,2],[319,134],[321,135],[323,136],[318,137],[316,2],[320,138],[541,2],[537,2],[543,139],[542,2],[313,140],[532,141],[535,142],[303,143],[302,144],[301,145],[548,9],[300,146],[289,2],[550,2],[571,147],[570,2],[551,9],[552,148],[227,2],[391,149],[392,150],[393,151],[231,2],[396,2],[251,152],[226,2],[475,9],[233,153],[474,154],[473,155],[464,2],[465,2],[472,2],[467,2],[470,156],[466,2],[468,157],[471,158],[469,157],[264,2],[261,2],[262,117],[416,2],[421,159],[422,160],[420,161],[418,162],[419,163],[414,2],[481,125],[256,125],[510,164],[517,165],[521,166],[350,167],[349,2],[344,2],[497,168],[505,169],[377,170],[378,171],[456,172],[366,2],[479,173],[354,9],[371,174],[482,175],[367,2],[370,176],[368,2],[480,177],[477,178],[476,2],[478,2],[374,2],[452,179],[239,180],[352,181],[356,182],[372,183],[375,184],[364,185],[357,186],[504,187],[430,188],[348,189],[236,190],[503,191],[232,192],[423,193],[415,2],[424,194],[441,195],[413,2],[440,196],[98,2],[435,197],[260,2],[455,198],[431,2],[245,2],[247,2],[386,2],[439,199],[263,2],[287,200],[373,201],[293,202],[353,2],[438,2],[417,2],[443,203],[444,204],[385,2],[446,205],[448,206],[447,207],[397,2],[437,190],[450,208],[347,209],[436,210],[442,211],[272,2],[276,2],[275,2],[274,2],[279,2],[273,2],[282,2],[281,2],[278,2],[277,2],[280,2],[283,212],[271,2],[339,213],[338,2],[343,214],[340,215],[342,216],[345,214],[341,215],[252,217],[331,218],[500,219],[498,2],[527,220],[529,221],[493,222],[528,223],[240,224],[237,224],[270,2],[254,225],[253,226],[249,227],[250,228],[258,229],[286,229],[297,229],[333,230],[298,230],[242,231],[241,2],[337,232],[336,233],[335,234],[334,235],[243,236],[484,237],[285,238],[492,239],[457,240],[487,241],[491,242],[380,243],[379,244],[360,245],[346,246],[328,247],[330,248],[327,249],[449,250],[351,2],[515,2],[248,251],[451,252],[499,253],[358,2],[288,254],[365,255],[363,256],[290,257],[425,258],[494,2],[291,259],[426,259],[513,2],[512,2],[514,2],[496,2],[495,2],[428,260],[355,2],[325,261],[246,262],[304,2],[230,263],[292,2],[519,9],[229,2],[531,264],[312,9],[525,125],[311,265],[508,266],[309,264],[234,2],[533,267],[307,9],[308,9],[299,2],[228,2],[306,268],[305,269],[294,270],[369,51],[429,51],[445,2],[433,271],[432,2],[317,140],[238,2],[326,9],[502,152],[509,272],[93,9],[96,273],[97,274],[94,9],[95,2],[406,73],[401,275],[400,2],[399,276],[398,2],[507,277],[518,278],[520,279],[524,280],[572,281],[526,282],[530,283],[558,284],[534,284],[557,285],[536,286],[546,287],[547,288],[549,289],[553,290],[556,152],[555,2],[554,291],[652,2],[650,292],[654,293],[721,294],[716,295],[619,296],[687,297],[680,298],[737,299],[617,300],[686,301],[675,302],[720,303],[717,304],[669,305],[679,306],[722,307],[723,307],[724,308],[732,309],[726,309],[734,309],[738,309],[725,309],[727,309],[730,309],[733,309],[729,310],[731,309],[735,311],[728,312],[629,313],[701,9],[698,314],[702,9],[640,309],[630,309],[693,315],[618,316],[639,317],[643,318],[700,309],[615,9],[699,319],[697,9],[696,309],[631,9],[743,320],[711,312],[691,321],[747,322],[709,2],[707,2],[712,323],[710,324],[706,325],[708,326],[713,327],[715,328],[705,9],[638,329],[614,309],[704,309],[653,330],[703,9],[678,329],[736,309],[671,331],[627,332],[632,333],[681,334],[683,331],[662,335],[665,331],[644,336],[664,337],[673,338],[674,339],[670,340],[684,341],[672,342],[649,343],[692,344],[688,345],[689,346],[685,347],[663,348],[651,349],[656,350],[633,351],[660,352],[661,353],[657,354],[634,355],[645,356],[682,339],[628,357],[690,2],[655,358],[648,359],[676,2],[745,360],[746,361],[718,2],[744,362],[739,2],[667,2],[641,2],[714,363],[668,2],[620,362],[742,364],[647,365],[677,366],[646,367],[719,368],[658,2],[694,2],[695,369],[642,2],[659,2],[740,2],[616,9],[624,370],[621,2],[623,2],[434,371],[566,2],[88,2],[89,2],[14,2],[15,2],[17,2],[16,2],[2,2],[18,2],[19,2],[20,2],[21,2],[22,2],[23,2],[24,2],[25,2],[3,2],[26,2],[27,2],[4,2],[28,2],[32,2],[29,2],[30,2],[31,2],[33,2],[34,2],[35,2],[5,2],[36,2],[37,2],[38,2],[39,2],[6,2],[43,2],[40,2],[41,2],[42,2],[44,2],[7,2],[45,2],[50,2],[51,2],[46,2],[47,2],[48,2],[49,2],[8,2],[55,2],[52,2],[53,2],[54,2],[56,2],[9,2],[57,2],[58,2],[59,2],[61,2],[60,2],[62,2],[63,2],[10,2],[64,2],[65,2],[66,2],[11,2],[67,2],[68,2],[69,2],[70,2],[71,2],[72,2],[12,2],[73,2],[74,2],[75,2],[76,2],[77,2],[1,2],[78,2],[79,2],[13,2],[80,2],[81,2],[82,2],[83,2],[84,2],[85,2],[86,2],[87,2],[128,372],[140,373],[125,374],[141,375],[150,376],[116,377],[117,378],[115,379],[149,291],[144,380],[148,381],[119,382],[137,383],[118,384],[147,385],[113,386],[114,380],[120,387],[121,2],[127,388],[124,387],[111,389],[151,390],[142,391],[131,392],[130,387],[132,393],[135,394],[129,395],[133,396],[145,291],[122,397],[123,398],[136,399],[112,375],[139,400],[138,387],[126,398],[134,401],[143,2],[110,2],[146,402],[613,403],[637,404],[608,405],[749,406],[754,407],[756,408],[753,409],[759,410],[760,411],[763,412],[762,413],[767,414],[766,414],[764,415],[769,416],[768,19],[765,417],[770,418],[596,419],[771,420],[597,421],[773,422],[774,423],[775,420],[777,424],[784,425],[783,426],[797,427],[779,428],[800,429],[799,430],[748,431],[574,432],[573,433],[801,434],[752,435],[757,436],[758,437],[603,438],[604,439],[607,440],[780,441],[781,442],[782,443],[761,444],[594,445],[593,446],[590,447],[595,448],[589,449],[772,450],[750,447],[610,451],[802,447],[598,451],[776,447],[751,452],[578,453],[609,441],[803,454],[755,441],[602,455],[788,456],[605,441],[806,457],[786,441],[587,458],[807,459],[796,460],[778,461],[606,462],[795,463],[791,464],[790,465],[794,466],[793,467],[789,447],[792,468],[785,447],[798,436],[562,125],[564,469],[563,19],[567,470],[568,19],[569,19]],"affectedFilesPendingEmit":[812,809,561,608,749,754,756,753,759,760,763,762,767,766,764,769,768,765,770,596,771,597,773,774,775,777,784,783,797,779,800,799,748,574,573,801,752,757,758,603,604,607,780,781,782,761,594,593,590,595,589,772,750,610,802,598,776,751,578,609,803,755,602,788,605,806,786,587,807,796,778,606,795,791,790,794,793,789,792,785,798,562,564,563,567,568,569],"version":"6.0.2"}
\ No newline at end of file
diff --git a/migrations/env.py b/migrations/env.py
index fb87a39..9c2d794 100644
--- a/migrations/env.py
+++ b/migrations/env.py
@@ -5,8 +5,8 @@
from alembic import context
from sqlalchemy import engine_from_config, pool
-import vidmation.models # noqa: F401 — ensure all models are registered for autogenerate
-from vidmation.models.base import Base
+import aividio.models # noqa: F401 — ensure all models are registered for autogenerate
+from aividio.models.base import Base
config = context.config
if config.config_file_name is not None:
diff --git a/migrations/versions/.gitkeep b/migrations/versions/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/pyproject.toml b/pyproject.toml
index a9b8371..61c137e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
-name = "vidmation"
+name = "aividio"
version = "0.1.0"
description = "AI-powered faceless YouTube video automation platform"
readme = "README.md"
@@ -91,19 +91,19 @@ dependencies = [
]
[project.urls]
-Homepage = "https://github.com/connorodea/vidmation"
-Repository = "https://github.com/connorodea/vidmation"
-Issues = "https://github.com/connorodea/vidmation/issues"
+Homepage = "https://github.com/connorodea/aividio"
+Repository = "https://github.com/connorodea/aividio"
+Issues = "https://github.com/connorodea/aividio/issues"
[project.optional-dependencies]
redis = ["redis>=5.0", "rq>=1.16"]
dev = ["pytest>=8.0", "pytest-asyncio", "ruff", "mypy"]
[project.scripts]
-vidmation = "vidmation.cli.app:app"
+aividio = "aividio.cli.app:app"
[tool.hatch.build.targets.wheel]
-packages = ["src/vidmation"]
+packages = ["src/aividio"]
[tool.ruff]
target-version = "py311"
diff --git a/scripts/assemble_video.py b/scripts/assemble_video.py
index 7f496d2..7435ce2 100755
--- a/scripts/assemble_video.py
+++ b/scripts/assemble_video.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Production video assembler — creates a real 8-10 min faceless YouTube video.
-Usage: cd ~/Developer/vidmation && python3 scripts/assemble_video.py
+Usage: cd ~/Developer/aividio && python3 scripts/assemble_video.py
"""
import json, subprocess, math, os, sys, time, shutil
from pathlib import Path
@@ -10,7 +10,7 @@
TARGET_MINUTES = 8
WORK = Path("data/work/production")
OUTPUT = Path("output/spiritual_awakening_production.mp4")
-PEXELS_KEY = os.environ.get("VIDMATION_PEXELS_API_KEY", "")
+PEXELS_KEY = os.environ.get("AIVIDIO_PEXELS_API_KEY", "")
FPS = 30
MUSIC_VOL = 0.12
WORK.mkdir(parents=True, exist_ok=True)
@@ -27,7 +27,7 @@ def srt_ts(t):
return f"{int(t//3600):02d}:{int((t%3600)//60):02d}:{int(t%60):02d},{int((t%1)*1000):03d}"
print("="*60)
-print(" VIDMATION Production Pipeline")
+print(" AIVIDIO Production Pipeline")
print("="*60)
t0 = time.time()
diff --git a/src/aividio/__init__.py b/src/aividio/__init__.py
new file mode 100644
index 0000000..e1ad995
--- /dev/null
+++ b/src/aividio/__init__.py
@@ -0,0 +1,3 @@
+"""AIVIDIO - AI-powered faceless YouTube video automation platform."""
+
+__version__ = "0.1.0"
diff --git a/src/aividio/__main__.py b/src/aividio/__main__.py
new file mode 100644
index 0000000..5991190
--- /dev/null
+++ b/src/aividio/__main__.py
@@ -0,0 +1,5 @@
+"""Allow running as `python -m aividio`."""
+
+from aividio.cli.app import app
+
+app()
diff --git a/src/vidmation/agent/__init__.py b/src/aividio/agent/__init__.py
similarity index 78%
rename from src/vidmation/agent/__init__.py
rename to src/aividio/agent/__init__.py
index 1f558f0..acc3fe5 100644
--- a/src/vidmation/agent/__init__.py
+++ b/src/aividio/agent/__init__.py
@@ -8,8 +8,8 @@
Usage::
- from vidmation.agent.orchestrator import AgentOrchestrator
- from vidmation.config.profiles import get_default_profile
+ from aividio.agent.orchestrator import AgentOrchestrator
+ from aividio.config.profiles import get_default_profile
agent = AgentOrchestrator()
ctx = agent.create_video(
@@ -19,6 +19,6 @@
print(ctx.final_video_path)
"""
-from vidmation.agent.orchestrator import AgentOrchestrator
+from aividio.agent.orchestrator import AgentOrchestrator
__all__ = ["AgentOrchestrator"]
diff --git a/src/vidmation/agent/config.py b/src/aividio/agent/config.py
similarity index 98%
rename from src/vidmation/agent/config.py
rename to src/aividio/agent/config.py
index 03ffb92..07d167a 100644
--- a/src/vidmation/agent/config.py
+++ b/src/aividio/agent/config.py
@@ -5,7 +5,7 @@
Usage::
- from vidmation.agent.config import AgentConfig
+ from aividio.agent.config import AgentConfig
config = AgentConfig(
max_budget_usd=3.0,
@@ -22,7 +22,7 @@
@dataclass
class AgentConfig:
- """Configuration for the VIDMATION AI agent orchestrator.
+ """Configuration for the AIVIDIO AI agent orchestrator.
Controls model selection, iteration limits, budget caps, feature flags,
and tool category filtering.
diff --git a/src/vidmation/agent/mcp.py b/src/aividio/agent/mcp.py
similarity index 96%
rename from src/vidmation/agent/mcp.py
rename to src/aividio/agent/mcp.py
index e374587..fc15034 100644
--- a/src/vidmation/agent/mcp.py
+++ b/src/aividio/agent/mcp.py
@@ -1,4 +1,4 @@
-"""MCP Server connector architecture for VIDMATION.
+"""MCP Server connector architecture for AIVIDIO.
Allows the AI agent to connect to external MCP (Model Context Protocol) servers
for additional capabilities beyond the built-in tool registry.
@@ -12,7 +12,7 @@
MCP is Anthropic's open protocol for connecting AI models to external tools and
data sources. This module provides the connector layer that discovers tools from
-MCP servers and registers them into the VIDMATION :class:`ToolRegistry` so they
+MCP servers and registers them into the AIVIDIO :class:`ToolRegistry` so they
are callable alongside the native tools.
Architecture:
@@ -33,9 +33,9 @@
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
- from vidmation.agent.registry import ToolRegistry
+ from aividio.agent.registry import ToolRegistry
-logger = logging.getLogger("vidmation.agent.mcp")
+logger = logging.getLogger("aividio.agent.mcp")
# ---------------------------------------------------------------------------
@@ -81,7 +81,7 @@ class MCPConnector:
Usage::
- from vidmation.agent.mcp import MCPConnector, KNOWN_MCP_SERVERS
+ from aividio.agent.mcp import MCPConnector, KNOWN_MCP_SERVERS
connector = MCPConnector(registry=tool_registry)
@@ -112,7 +112,7 @@ def register_server(self, config: MCPServerConfig) -> int:
Connects to the server, discovers available tools via the MCP
``tools/list`` method, and creates corresponding
- :class:`~vidmation.agent.registry.ToolDefinition` entries in the
+ :class:`~aividio.agent.registry.ToolDefinition` entries in the
registry.
Args:
@@ -156,7 +156,7 @@ def register_server(self, config: MCPServerConfig) -> int:
self._server_tools[config.name] = tools
- # Register each discovered tool into the VIDMATION tool registry
+ # Register each discovered tool into the AIVIDIO tool registry
imported = 0
for tool in tools:
self._register_mcp_tool(tool)
@@ -332,7 +332,7 @@ def get_server_tools(self, server_name: str) -> list[MCPTool]:
def _register_mcp_tool(self, mcp_tool: MCPTool) -> None:
"""Wrap an MCP tool as a ToolDefinition and register it."""
- from vidmation.agent.registry import ToolDefinition
+ from aividio.agent.registry import ToolDefinition
# Prefix the tool name with the server name to avoid collisions
prefixed_name = f"mcp_{mcp_tool.server_name}_{mcp_tool.name}"
@@ -468,7 +468,7 @@ def _close_connection(self, connection: Any) -> None:
capabilities=["read_file", "write_file", "list_directory", "create_directory"],
transport="stdio",
command="npx",
- args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp/vidmation"],
+ args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp/aividio"],
),
"web_search": MCPServerConfig(
name="web_search",
@@ -491,11 +491,11 @@ def _close_connection(self, connection: Any) -> None:
"database": MCPServerConfig(
name="database",
url="",
- description="Direct SQL access to the VIDMATION database",
+ description="Direct SQL access to the AIVIDIO database",
capabilities=["query", "execute", "list_tables", "describe_table"],
transport="stdio",
command="npx",
- args=["-y", "@modelcontextprotocol/server-sqlite", "data/vidmation.db"],
+ args=["-y", "@modelcontextprotocol/server-sqlite", "data/aividio.db"],
),
"youtube_data": MCPServerConfig(
name="youtube_data",
diff --git a/src/vidmation/agent/orchestrator.py b/src/aividio/agent/orchestrator.py
similarity index 98%
rename from src/vidmation/agent/orchestrator.py
rename to src/aividio/agent/orchestrator.py
index 0384a4c..51806d1 100644
--- a/src/vidmation/agent/orchestrator.py
+++ b/src/aividio/agent/orchestrator.py
@@ -17,16 +17,16 @@
import anthropic
-from vidmation.agent.prompts import (
+from aividio.agent.prompts import (
PRODUCTION_PLAN_PROMPT,
REVIEW_PROMPT,
SYSTEM_PROMPT,
)
-from vidmation.agent.tools import AgentToolkit
-from vidmation.config.profiles import ChannelProfile, get_default_profile
-from vidmation.config.settings import Settings, get_settings
-from vidmation.models.video import VideoFormat
-from vidmation.pipeline.context import PipelineContext
+from aividio.agent.tools import AgentToolkit
+from aividio.config.profiles import ChannelProfile, get_default_profile
+from aividio.config.settings import Settings, get_settings
+from aividio.models.video import VideoFormat
+from aividio.pipeline.context import PipelineContext
logger = logging.getLogger(__name__)
@@ -51,7 +51,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"anthropic_api_key is required for the AI agent. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
self.client = anthropic.Anthropic(api_key=api_key)
self.tools = self._build_tool_definitions()
@@ -107,7 +107,7 @@ def create_video(
# Get available caption template names for the prompt
try:
- from vidmation.captions.templates import list_templates
+ from aividio.captions.templates import list_templates
template_names = ", ".join(t["name"] for t in list_templates()[:20])
except Exception:
template_names = "bold_centered, hormozi, mrbeast, finance_serious, tech_modern, motivation_fire, education_clear"
@@ -175,7 +175,7 @@ def plan_video(
profile = channel_profile or get_default_profile()
try:
- from vidmation.captions.templates import list_templates
+ from aividio.captions.templates import list_templates
template_names = ", ".join(t["name"] for t in list_templates()[:20])
except Exception:
template_names = "bold_centered, hormozi, mrbeast, finance_serious, tech_modern"
diff --git a/src/vidmation/agent/power_tools/__init__.py b/src/aividio/agent/power_tools/__init__.py
similarity index 69%
rename from src/vidmation/agent/power_tools/__init__.py
rename to src/aividio/agent/power_tools/__init__.py
index 6c1a07d..91552ae 100644
--- a/src/vidmation/agent/power_tools/__init__.py
+++ b/src/aividio/agent/power_tools/__init__.py
@@ -10,7 +10,7 @@
Usage::
- from vidmation.agent.power_tools import PowerToolsAgent
+ from aividio.agent.power_tools import PowerToolsAgent
agent = PowerToolsAgent()
result = agent.execute_task(
@@ -19,10 +19,10 @@
print(result["output_files"])
"""
-from vidmation.agent.power_tools.agent import PowerToolsAgent
-from vidmation.agent.power_tools.executors import CommandExecutor
-from vidmation.agent.power_tools.precheck import check_all_tools
-from vidmation.agent.power_tools.recipes import PowerToolRecipes
+from aividio.agent.power_tools.agent import PowerToolsAgent
+from aividio.agent.power_tools.executors import CommandExecutor
+from aividio.agent.power_tools.precheck import check_all_tools
+from aividio.agent.power_tools.recipes import PowerToolRecipes
__all__ = [
"PowerToolsAgent",
diff --git a/src/vidmation/agent/power_tools/agent.py b/src/aividio/agent/power_tools/agent.py
similarity index 99%
rename from src/vidmation/agent/power_tools/agent.py
rename to src/aividio/agent/power_tools/agent.py
index 9a4dcfb..7c6119c 100644
--- a/src/vidmation/agent/power_tools/agent.py
+++ b/src/aividio/agent/power_tools/agent.py
@@ -8,7 +8,7 @@
Usage::
- from vidmation.agent.power_tools import PowerToolsAgent
+ from aividio.agent.power_tools import PowerToolsAgent
agent = PowerToolsAgent(work_dir=Path("/tmp/pt_work"))
result = agent.execute_task(
@@ -28,10 +28,10 @@
import anthropic
-from vidmation.agent.power_tools.capabilities import get_all_power_tools
-from vidmation.agent.power_tools.executors import CommandExecutor
-from vidmation.agent.power_tools.precheck import get_installed_tools
-from vidmation.config.settings import get_settings
+from aividio.agent.power_tools.capabilities import get_all_power_tools
+from aividio.agent.power_tools.executors import CommandExecutor
+from aividio.agent.power_tools.precheck import get_installed_tools
+from aividio.config.settings import get_settings
logger = logging.getLogger(__name__)
@@ -42,7 +42,7 @@
_MODEL = "claude-sonnet-4-20250514"
POWER_TOOLS_SYSTEM_PROMPT = """\
-You are VIDMATION's Power Tools Agent -- a specialized media manipulation expert
+You are AIVIDIO's Power Tools Agent -- a specialized media manipulation expert
with direct access to command-line tools.
You can execute any of the following CLI tools to transform images, video, and audio:
diff --git a/src/vidmation/agent/power_tools/capabilities.py b/src/aividio/agent/power_tools/capabilities.py
similarity index 100%
rename from src/vidmation/agent/power_tools/capabilities.py
rename to src/aividio/agent/power_tools/capabilities.py
diff --git a/src/vidmation/agent/power_tools/executors.py b/src/aividio/agent/power_tools/executors.py
similarity index 100%
rename from src/vidmation/agent/power_tools/executors.py
rename to src/aividio/agent/power_tools/executors.py
diff --git a/src/vidmation/agent/power_tools/precheck.py b/src/aividio/agent/power_tools/precheck.py
similarity index 99%
rename from src/vidmation/agent/power_tools/precheck.py
rename to src/aividio/agent/power_tools/precheck.py
index 90e26dd..7105775 100644
--- a/src/vidmation/agent/power_tools/precheck.py
+++ b/src/aividio/agent/power_tools/precheck.py
@@ -233,7 +233,7 @@ def get_imagemagick_formats() -> list[str]:
def preflight_report() -> str:
"""Generate a human-readable report of tool availability."""
tools = check_all_tools()
- lines = ["VIDMATION Power Tools -- Pre-flight Check", "=" * 50, ""]
+ lines = ["AIVIDIO Power Tools -- Pre-flight Check", "=" * 50, ""]
# Core tools first.
lines.append("CORE TOOLS (required):")
diff --git a/src/vidmation/agent/power_tools/recipes.py b/src/aividio/agent/power_tools/recipes.py
similarity index 99%
rename from src/vidmation/agent/power_tools/recipes.py
rename to src/aividio/agent/power_tools/recipes.py
index d96796f..a580588 100644
--- a/src/vidmation/agent/power_tools/recipes.py
+++ b/src/aividio/agent/power_tools/recipes.py
@@ -11,7 +11,7 @@
import logging
from pathlib import Path
-from vidmation.agent.power_tools.executors import CommandExecutor
+from aividio.agent.power_tools.executors import CommandExecutor
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/agent/prompts.py b/src/aividio/agent/prompts.py
similarity index 98%
rename from src/vidmation/agent/prompts.py
rename to src/aividio/agent/prompts.py
index 004c878..9a1cbf8 100644
--- a/src/vidmation/agent/prompts.py
+++ b/src/aividio/agent/prompts.py
@@ -3,7 +3,7 @@
from __future__ import annotations
SYSTEM_PROMPT = """\
-You are VIDMATION's AI Video Production Director. You coordinate the creation
+You are AIVIDIO's AI Video Production Director. You coordinate the creation
of faceless YouTube videos that meet monetisation requirements.
You have access to the following tools to create videos:
diff --git a/src/vidmation/agent/registry.py b/src/aividio/agent/registry.py
similarity index 94%
rename from src/vidmation/agent/registry.py
rename to src/aividio/agent/registry.py
index 1e39d96..f971da9 100644
--- a/src/vidmation/agent/registry.py
+++ b/src/aividio/agent/registry.py
@@ -1,6 +1,6 @@
"""Complete tool registry for the AI Agent.
-Maps every VIDMATION capability to a Claude tool_use compatible definition.
+Maps every AIVIDIO capability to a Claude tool_use compatible definition.
Each tool has: name, description, input_schema (JSON Schema), and an executor function.
Categories:
@@ -32,10 +32,10 @@
from typing import TYPE_CHECKING, Any, Callable
if TYPE_CHECKING:
- from vidmation.config.profiles import ChannelProfile
- from vidmation.config.settings import Settings
+ from aividio.config.profiles import ChannelProfile
+ from aividio.config.settings import Settings
-logger = logging.getLogger("vidmation.agent.registry")
+logger = logging.getLogger("aividio.agent.registry")
# ---------------------------------------------------------------------------
@@ -64,7 +64,7 @@ class ToolDefinition:
class ToolRegistry:
"""Central registry of all tools available to the AI agent.
- On initialisation, every service in the VIDMATION codebase is registered
+ On initialisation, every service in the AIVIDIO codebase is registered
as a callable tool with a JSON-Schema input definition and an executor
function that catches exceptions and returns a string result suitable for
Claude's ``tool_result`` content block.
@@ -209,7 +209,7 @@ def _generate_script(
provider: str = "claude",
**kwargs: Any,
) -> str:
- from vidmation.services.scriptgen import create_script_generator
+ from aividio.services.scriptgen import create_script_generator
gen = create_script_generator(
provider=provider, settings=self.settings
@@ -250,7 +250,7 @@ def _generate_script(
# -- optimize_retention --------------------------------------------
def _optimize_retention(script: str, **kwargs: Any) -> str:
- from vidmation.services.scriptgen.retention import RetentionOptimizer
+ from aividio.services.scriptgen.retention import RetentionOptimizer
optimizer = RetentionOptimizer(settings=self.settings)
script_dict = json.loads(script)
@@ -284,7 +284,7 @@ def _optimize_retention(script: str, **kwargs: Any) -> str:
# -- generate_hooks ------------------------------------------------
def _generate_hooks(topic: str, count: int = 5, **kwargs: Any) -> str:
- from vidmation.services.scriptgen.retention import RetentionOptimizer
+ from aividio.services.scriptgen.retention import RetentionOptimizer
optimizer = RetentionOptimizer(settings=self.settings)
hooks = optimizer.generate_hooks(topic=topic, count=count)
@@ -324,7 +324,7 @@ def _generate_hooks(topic: str, count: int = 5, **kwargs: Any) -> str:
# -- generate_titles -----------------------------------------------
def _generate_titles(script: str, count: int = 10, **kwargs: Any) -> str:
- from vidmation.services.scriptgen.retention import RetentionOptimizer
+ from aividio.services.scriptgen.retention import RetentionOptimizer
optimizer = RetentionOptimizer(settings=self.settings)
script_dict = json.loads(script)
@@ -366,7 +366,7 @@ def _generate_titles(script: str, count: int = 10, **kwargs: Any) -> str:
def _generate_prompt_pack(
topic: str, script: str, **kwargs: Any
) -> str:
- from vidmation.services.scriptgen.prompt_packs import (
+ from aividio.services.scriptgen.prompt_packs import (
PromptPackGenerator,
)
@@ -420,8 +420,8 @@ def _synthesize_speech(
speed: float = 1.0,
**kwargs: Any,
) -> str:
- from vidmation.config.profiles import VoiceConfig
- from vidmation.services.tts import create_tts_provider
+ from aividio.config.profiles import VoiceConfig
+ from aividio.services.tts import create_tts_provider
tts = create_tts_provider(provider=provider, settings=self.settings)
vc = VoiceConfig(
@@ -431,7 +431,7 @@ def _synthesize_speech(
similarity_boost=self.profile.voice.similarity_boost,
speed=speed,
)
- out = Path(output_path) if output_path else Path(f"/tmp/vidmation_tts_{hash(text[:50])}.mp3")
+ out = Path(output_path) if output_path else Path(f"/tmp/aividio_tts_{hash(text[:50])}.mp3")
path, duration = tts.synthesize(text=text, voice_config=vc, output_path=out)
return json.dumps({"path": str(path), "duration_seconds": duration})
@@ -482,7 +482,7 @@ def _synthesize_speech(
# -- list_voices ---------------------------------------------------
def _list_voices(provider: str = "elevenlabs", **kwargs: Any) -> str:
- from vidmation.services.tts import create_tts_provider
+ from aividio.services.tts import create_tts_provider
tts = create_tts_provider(provider=provider, settings=self.settings)
voices = tts.list_voices()
@@ -517,7 +517,7 @@ def _clone_voice(
provider: str = "elevenlabs",
**kwargs: Any,
) -> str:
- from vidmation.services.tts.voice_cloning import VoiceCloner
+ from aividio.services.tts.voice_cloning import VoiceCloner
cloner = VoiceCloner(settings=self.settings)
result = cloner.clone_voice(
@@ -575,7 +575,7 @@ def _preview_voice(
provider: str = "elevenlabs",
**kwargs: Any,
) -> str:
- from vidmation.services.tts.voice_cloning import VoiceCloner
+ from aividio.services.tts.voice_cloning import VoiceCloner
cloner = VoiceCloner(settings=self.settings)
path = cloner.preview_voice(
@@ -620,8 +620,8 @@ def _synthesize_section(
output_dir: str = "",
**kwargs: Any,
) -> str:
- from vidmation.config.profiles import VoiceConfig
- from vidmation.services.tts import create_tts_provider
+ from aividio.config.profiles import VoiceConfig
+ from aividio.services.tts import create_tts_provider
sections_list = json.loads(sections)
if section_index < 0 or section_index >= len(sections_list):
@@ -638,7 +638,7 @@ def _synthesize_section(
similarity_boost=self.profile.voice.similarity_boost,
speed=self.profile.voice.speed,
)
- out_dir = Path(output_dir) if output_dir else Path("/tmp/vidmation_sections")
+ out_dir = Path(output_dir) if output_dir else Path("/tmp/aividio_sections")
out_dir.mkdir(parents=True, exist_ok=True)
out_path = out_dir / f"section_{section_index:03d}.mp3"
@@ -697,7 +697,7 @@ def _concatenate_audio(
if not p.exists():
return json.dumps({"error": f"Audio file not found: {p}"})
- out = Path(output_path) if output_path else Path("/tmp/vidmation_concat.mp3")
+ out = Path(output_path) if output_path else Path("/tmp/aividio_concat.mp3")
out.parent.mkdir(parents=True, exist_ok=True)
# Build concat list
@@ -717,7 +717,7 @@ def _concatenate_audio(
stderr = exc.stderr.decode(errors="replace") if exc.stderr else ""
return json.dumps({"error": f"Concatenation failed: {stderr}"})
- from vidmation.utils.ffmpeg import get_duration
+ from aividio.utils.ffmpeg import get_duration
duration = get_duration(out)
return json.dumps({"path": str(out), "duration_seconds": duration})
@@ -757,7 +757,7 @@ def _register_media_tools(self) -> None:
def _search_stock_video(
query: str, count: int = 5, provider: str = "pexels", **kwargs: Any
) -> str:
- from vidmation.services.media import create_media_provider
+ from aividio.services.media import create_media_provider
media = create_media_provider(provider=provider, settings=self.settings)
results = media.search_videos(query=query, count=count)
@@ -787,7 +787,7 @@ def _search_stock_video(
def _search_stock_image(
query: str, count: int = 5, provider: str = "pexels", **kwargs: Any
) -> str:
- from vidmation.services.media import create_media_provider
+ from aividio.services.media import create_media_provider
media = create_media_provider(provider=provider, settings=self.settings)
results = media.search_images(query=query, count=count)
@@ -817,7 +817,7 @@ def _search_stock_image(
def _download_media(
url: str, output_path: str, provider: str = "pexels", **kwargs: Any
) -> str:
- from vidmation.services.media import create_media_provider
+ from aividio.services.media import create_media_provider
media = create_media_provider(provider=provider, settings=self.settings)
result = media.download(url=url, output_path=Path(output_path))
@@ -846,12 +846,12 @@ def _download_media(
def _search_and_download(
query: str,
media_type: str = "video",
- output_dir: str = "/tmp/vidmation_media",
+ output_dir: str = "/tmp/aividio_media",
section_index: int = 0,
provider: str = "pexels",
**kwargs: Any,
) -> str:
- from vidmation.services.media import create_media_provider
+ from aividio.services.media import create_media_provider
media = create_media_provider(provider=provider, settings=self.settings)
result = media.search_and_download(
@@ -891,11 +891,11 @@ def _search_and_download(
# -- bulk_source_media ---------------------------------------------
def _bulk_source_media(
sections: str,
- output_dir: str = "/tmp/vidmation_media",
+ output_dir: str = "/tmp/aividio_media",
provider: str = "pexels",
**kwargs: Any,
) -> str:
- from vidmation.services.media import create_media_provider
+ from aividio.services.media import create_media_provider
media = create_media_provider(provider=provider, settings=self.settings)
sections_list = json.loads(sections)
@@ -950,7 +950,7 @@ def _register_imagegen_tools(self) -> None:
def _generate_image_dalle(
prompt: str, size: str = "1792x1024", output_path: str = "", **kwargs: Any
) -> str:
- from vidmation.services.imagegen.dalle import DalleImageGenerator
+ from aividio.services.imagegen.dalle import DalleImageGenerator
gen = DalleImageGenerator(settings=self.settings)
out = Path(output_path) if output_path else None
@@ -985,7 +985,7 @@ def _generate_image_replicate(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.services.imagegen.replicate_gen import ReplicateImageGenerator
+ from aividio.services.imagegen.replicate_gen import ReplicateImageGenerator
gen = ReplicateImageGenerator(settings=self.settings, model_id=model)
out = Path(output_path) if output_path else None
@@ -1021,7 +1021,7 @@ def _generate_image_fal(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.services.imagegen.fal_gen import FalImageGenerator
+ from aividio.services.imagegen.fal_gen import FalImageGenerator
gen = FalImageGenerator(settings=self.settings, model_id=model)
out = Path(output_path) if output_path else None
@@ -1057,7 +1057,7 @@ def _generate_thumbnail(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.services.scriptgen.retention import RetentionOptimizer
+ from aividio.services.scriptgen.retention import RetentionOptimizer
optimizer = RetentionOptimizer(settings=self.settings)
script_dict = json.loads(script) if script else {"title": title}
@@ -1069,7 +1069,7 @@ def _generate_thumbnail(
concept = concepts[0]
prompt = concept.get("image_prompt", f"YouTube thumbnail for: {title}")
- from vidmation.services.imagegen import create_image_generator
+ from aividio.services.imagegen import create_image_generator
gen = create_image_generator(
provider=self.settings.default_image_provider,
@@ -1121,7 +1121,7 @@ def _generate_video_clip(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.services.videogen import create_video_generator
+ from aividio.services.videogen import create_video_generator
gen = create_video_generator(
provider=provider,
@@ -1168,7 +1168,7 @@ def _generate_video_from_image(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.services.videogen import create_video_generator
+ from aividio.services.videogen import create_video_generator
gen = create_video_generator(
provider=provider,
@@ -1215,7 +1215,7 @@ def _generate_local_video(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.services.videogen.local_gen import LocalVideoGenerator
+ from aividio.services.videogen.local_gen import LocalVideoGenerator
gen = LocalVideoGenerator(settings=self.settings)
out = Path(output_path) if output_path else None
@@ -1252,7 +1252,7 @@ def _generate_batch_clips(
parallel: bool = True,
**kwargs: Any,
) -> str:
- from vidmation.services.models.orchestrator import ModelOrchestrator
+ from aividio.services.models.orchestrator import ModelOrchestrator
orch = ModelOrchestrator(settings=self.settings)
sections_list = json.loads(sections)
@@ -1289,7 +1289,7 @@ def _generate_batch_clips(
# -- estimate_video_cost -------------------------------------------
def _estimate_video_cost(sections: str, **kwargs: Any) -> str:
- from vidmation.services.models.orchestrator import ModelOrchestrator
+ from aividio.services.models.orchestrator import ModelOrchestrator
orch = ModelOrchestrator(settings=self.settings)
sections_list = json.loads(sections)
@@ -1328,11 +1328,11 @@ def _assemble_full_video(
work_dir: str = "",
**kwargs: Any,
) -> str:
- from vidmation.video.assembler import VideoAssembler
+ from aividio.video.assembler import VideoAssembler
sections_list = json.loads(sections)
words = json.loads(word_timestamps) if word_timestamps else []
- w_dir = Path(work_dir) if work_dir else Path("/tmp/vidmation_assembly")
+ w_dir = Path(work_dir) if work_dir else Path("/tmp/aividio_assembly")
assembler = VideoAssembler(
video_config=self.profile.video, work_dir=w_dir
)
@@ -1380,7 +1380,7 @@ def _mix_audio(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.video.audio_mixer import mix_voiceover_and_music
+ from aividio.video.audio_mixer import mix_voiceover_and_music
out = Path(output_path) if output_path else None
path = mix_voiceover_and_music(
@@ -1415,7 +1415,7 @@ def _mix_audio(
def _normalize_audio(
audio_path: str, target_lufs: float = -16.0, output_path: str = "", **kwargs: Any
) -> str:
- from vidmation.video.audio_mixer import normalize_audio
+ from aividio.video.audio_mixer import normalize_audio
out = Path(output_path) if output_path else None
path = normalize_audio(
@@ -1448,7 +1448,7 @@ def _fit_clip_to_duration(
) -> str:
import ffmpeg as _ffmpeg
- from vidmation.utils.ffmpeg import get_duration as _get_dur
+ from aividio.utils.ffmpeg import get_duration as _get_dur
src = Path(clip_path)
out = Path(output_path) if output_path else src.with_stem(f"{src.stem}_fitted")
@@ -1492,7 +1492,7 @@ def _fit_clip_to_duration(
def _apply_ken_burns(
image_path: str, duration: float = 5.0, output_path: str = "", **kwargs: Any
) -> str:
- from vidmation.services.videogen.local_gen import LocalVideoGenerator
+ from aividio.services.videogen.local_gen import LocalVideoGenerator
gen = LocalVideoGenerator(settings=self.settings)
out = Path(output_path) if output_path else None
@@ -1529,7 +1529,7 @@ def _encode_final(
) -> str:
import ffmpeg as _ffmpeg
- from vidmation.video.formats import get_format
+ from aividio.video.formats import get_format
fmt = get_format(format)
src = Path(video_path)
@@ -1578,7 +1578,7 @@ def _register_caption_tools(self) -> None:
def _transcribe_whisper(
audio_path: str, backend: str = "replicate", **kwargs: Any
) -> str:
- from vidmation.services.captions.whisper import WhisperCaptionGenerator
+ from aividio.services.captions.whisper import WhisperCaptionGenerator
gen = WhisperCaptionGenerator(settings=self.settings, backend=backend)
words = gen.transcribe(Path(audio_path))
@@ -1608,7 +1608,7 @@ def _transcribe_whisper(
# -- list_caption_templates ----------------------------------------
def _list_caption_templates(**kwargs: Any) -> str:
- from vidmation.captions.templates import list_templates
+ from aividio.captions.templates import list_templates
templates = list_templates()
return json.dumps(
@@ -1634,7 +1634,7 @@ def _apply_caption_template(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.video.captions_render import render_with_template
+ from aividio.video.captions_render import render_with_template
words = json.loads(word_timestamps)
out = Path(output_path) if output_path else Path(video_path).with_stem(
@@ -1678,7 +1678,7 @@ def _generate_ass_subtitles(
template_name: str = "hormozi",
**kwargs: Any,
) -> str:
- from vidmation.video.captions_render import generate_animated_ass
+ from aividio.video.captions_render import generate_animated_ass
words = json.loads(word_timestamps)
path = generate_animated_ass(
@@ -1709,7 +1709,7 @@ def _generate_ass_subtitles(
def _burn_captions(
video_path: str, ass_path: str, output_path: str, **kwargs: Any
) -> str:
- from vidmation.video.captions_render import burn_captions
+ from aividio.video.captions_render import burn_captions
path = burn_captions(
video_path=Path(video_path),
@@ -1751,7 +1751,7 @@ def _apply_magic_zoom(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.effects.magic_zoom import MagicZoom
+ from aividio.effects.magic_zoom import MagicZoom
zoom = MagicZoom(settings=self.settings)
words = json.loads(word_timestamps)
@@ -1794,7 +1794,7 @@ def _apply_magic_zoom(
# -- detect_emphasis_points ----------------------------------------
def _detect_emphasis_points(word_timestamps: str, **kwargs: Any) -> str:
- from vidmation.effects.magic_zoom import MagicZoom
+ from aividio.effects.magic_zoom import MagicZoom
zoom = MagicZoom(settings=self.settings)
words = json.loads(word_timestamps)
@@ -1826,7 +1826,7 @@ def _remove_silence(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.effects.silence_remover import SilenceRemover
+ from aividio.effects.silence_remover import SilenceRemover
remover = SilenceRemover()
out = Path(output_path) if output_path else Path(video_path).with_stem(
@@ -1863,7 +1863,7 @@ def _remove_silence(
def _remove_filler_words(
video_path: str, word_timestamps: str, output_path: str = "", **kwargs: Any
) -> str:
- from vidmation.effects.silence_remover import SilenceRemover
+ from aividio.effects.silence_remover import SilenceRemover
remover = SilenceRemover()
words = json.loads(word_timestamps)
@@ -1902,7 +1902,7 @@ def _insert_broll(
output_path: str = "",
**kwargs: Any,
) -> str:
- from vidmation.effects.magic_broll import MagicBRoll
+ from aividio.effects.magic_broll import MagicBRoll
broll = MagicBRoll(settings=self.settings)
words = json.loads(word_timestamps)
@@ -1945,7 +1945,7 @@ def _insert_broll(
def _add_emojis(
video_path: str, word_timestamps: str, output_path: str = "", **kwargs: Any
) -> str:
- from vidmation.effects.emoji_sfx import EmojiSFXEngine
+ from aividio.effects.emoji_sfx import EmojiSFXEngine
engine = EmojiSFXEngine(settings=self.settings)
words = json.loads(word_timestamps)
@@ -1980,7 +1980,7 @@ def _add_emojis(
def _add_sound_effects(
audio_path: str, word_timestamps: str, output_path: str = "", **kwargs: Any
) -> str:
- from vidmation.effects.emoji_sfx import EmojiSFXEngine
+ from aividio.effects.emoji_sfx import EmojiSFXEngine
engine = EmojiSFXEngine(settings=self.settings)
words = json.loads(word_timestamps)
@@ -2021,7 +2021,7 @@ def _extract_viral_clips(
output_dir: str = "",
**kwargs: Any,
) -> str:
- from vidmation.effects.magic_clips import MagicClips
+ from aividio.effects.magic_clips import MagicClips
clipper = MagicClips(settings=self.settings)
words = json.loads(word_timestamps)
@@ -2067,7 +2067,7 @@ def _extract_viral_clips(
def _register_seo_tools(self) -> None:
# -- optimize_title ------------------------------------------------
def _optimize_title(title: str, topic: str = "", count: int = 5, **kwargs: Any) -> str:
- from vidmation.seo.optimizer import SEOOptimizer
+ from aividio.seo.optimizer import SEOOptimizer
seo = SEOOptimizer(settings=self.settings)
results = seo.optimize_titles(title=title, topic=topic or title, niche=self.profile.niche, count=count)
@@ -2077,7 +2077,7 @@ def _optimize_title(title: str, topic: str = "", count: int = 5, **kwargs: Any)
# -- optimize_description ------------------------------------------
def _optimize_description(script: str, **kwargs: Any) -> str:
- from vidmation.seo.optimizer import SEOOptimizer
+ from aividio.seo.optimizer import SEOOptimizer
seo = SEOOptimizer(settings=self.settings)
script_dict = json.loads(script)
@@ -2088,7 +2088,7 @@ def _optimize_description(script: str, **kwargs: Any) -> str:
# -- generate_tags -------------------------------------------------
def _generate_tags(script: str, **kwargs: Any) -> str:
- from vidmation.seo.optimizer import SEOOptimizer
+ from aividio.seo.optimizer import SEOOptimizer
seo = SEOOptimizer(settings=self.settings)
script_dict = json.loads(script)
@@ -2099,7 +2099,7 @@ def _generate_tags(script: str, **kwargs: Any) -> str:
# -- generate_hashtags ---------------------------------------------
def _generate_hashtags(script: str, platform: str = "youtube", **kwargs: Any) -> str:
- from vidmation.seo.hashtags import HashtagGenerator
+ from aividio.seo.hashtags import HashtagGenerator
gen = HashtagGenerator(settings=self.settings)
script_dict = json.loads(script)
@@ -2110,7 +2110,7 @@ def _generate_hashtags(script: str, platform: str = "youtube", **kwargs: Any) ->
# -- keyword_research ----------------------------------------------
def _keyword_research(topic: str, niche: str = "", **kwargs: Any) -> str:
- from vidmation.seo.optimizer import SEOOptimizer
+ from aividio.seo.optimizer import SEOOptimizer
seo = SEOOptimizer(settings=self.settings)
result = seo.keyword_research(topic=topic, niche=niche or self.profile.niche)
@@ -2120,7 +2120,7 @@ def _keyword_research(topic: str, niche: str = "", **kwargs: Any) -> str:
# -- generate_content_calendar -------------------------------------
def _generate_content_calendar(weeks: int = 4, **kwargs: Any) -> str:
- from vidmation.content.planner import ContentPlanner
+ from aividio.content.planner import ContentPlanner
planner = ContentPlanner(settings=self.settings)
calendar = planner.generate_calendar(channel=self.profile, weeks=weeks)
@@ -2130,7 +2130,7 @@ def _generate_content_calendar(weeks: int = 4, **kwargs: Any) -> str:
# -- suggest_trending_topics ---------------------------------------
def _suggest_trending_topics(niche: str = "", count: int = 10, **kwargs: Any) -> str:
- from vidmation.content.planner import ContentPlanner
+ from aividio.content.planner import ContentPlanner
planner = ContentPlanner(settings=self.settings)
topics = planner.suggest_trending(niche=niche or self.profile.niche, count=count)
@@ -2140,7 +2140,7 @@ def _suggest_trending_topics(niche: str = "", count: int = 10, **kwargs: Any) ->
# -- analyze_content_gaps ------------------------------------------
def _analyze_content_gaps(**kwargs: Any) -> str:
- from vidmation.content.planner import ContentPlanner
+ from aividio.content.planner import ContentPlanner
planner = ContentPlanner(settings=self.settings)
gaps = planner.analyze_gaps(channel=self.profile)
@@ -2155,7 +2155,7 @@ def _analyze_content_gaps(**kwargs: Any) -> str:
def _register_brand_tools(self) -> None:
# -- apply_brand_kit -----------------------------------------------
def _apply_brand_kit(video_path: str, output_path: str = "", **kwargs: Any) -> str:
- from vidmation.brand.kit import BrandKit
+ from aividio.brand.kit import BrandKit
kit = BrandKit.from_profile(self.profile) if hasattr(BrandKit, "from_profile") else BrandKit()
out = Path(output_path) if output_path else Path(video_path).with_stem(f"{Path(video_path).stem}_branded")
@@ -2166,7 +2166,7 @@ def _apply_brand_kit(video_path: str, output_path: str = "", **kwargs: Any) -> s
# -- add_logo_overlay ----------------------------------------------
def _add_logo_overlay(video_path: str, position: str = "top_right", opacity: float = 0.8, output_path: str = "", **kwargs: Any) -> str:
- from vidmation.brand.overlays import add_logo_overlay
+ from aividio.brand.overlays import add_logo_overlay
out = Path(output_path) if output_path else Path(video_path).with_stem(f"{Path(video_path).stem}_logo")
path = add_logo_overlay(video_path=Path(video_path), position=position, opacity=opacity, output_path=out)
@@ -2176,7 +2176,7 @@ def _add_logo_overlay(video_path: str, position: str = "top_right", opacity: flo
# -- add_lower_third -----------------------------------------------
def _add_lower_third(video_path: str, text: str, style: str = "default", output_path: str = "", **kwargs: Any) -> str:
- from vidmation.brand.overlays import add_lower_third
+ from aividio.brand.overlays import add_lower_third
out = Path(output_path) if output_path else Path(video_path).with_stem(f"{Path(video_path).stem}_lt")
path = add_lower_third(video_path=Path(video_path), text=text, style=style, output_path=out)
@@ -2186,7 +2186,7 @@ def _add_lower_third(video_path: str, text: str, style: str = "default", output_
# -- list_video_templates ------------------------------------------
def _list_video_templates(**kwargs: Any) -> str:
- from vidmation.brand.templates import list_templates
+ from aividio.brand.templates import list_templates
templates = list_templates()
return json.dumps(templates, default=str)
@@ -2195,7 +2195,7 @@ def _list_video_templates(**kwargs: Any) -> str:
# -- apply_video_template ------------------------------------------
def _apply_video_template(template_name: str, **kwargs: Any) -> str:
- from vidmation.brand.templates import get_template
+ from aividio.brand.templates import get_template
config = get_template(template_name)
return json.dumps(config, default=str)
@@ -2209,7 +2209,7 @@ def _apply_video_template(template_name: str, **kwargs: Any) -> str:
def _register_platform_tools(self) -> None:
def _make_export_tool(platform_name: str, description: str) -> Callable[..., str]:
def _export(video_path: str, output_dir: str = "", **kwargs: Any) -> str:
- from vidmation.platforms.exporter import MultiPlatformExporter
+ from aividio.platforms.exporter import MultiPlatformExporter
exporter = MultiPlatformExporter()
out_dir = Path(output_dir) if output_dir else Path(video_path).parent / "exports"
@@ -2227,7 +2227,7 @@ def _export(video_path: str, output_dir: str = "", **kwargs: Any) -> str:
# -- export_all_platforms ------------------------------------------
def _export_all_platforms(video_path: str, platforms: list[str] | None = None, output_dir: str = "", **kwargs: Any) -> str:
- from vidmation.platforms.exporter import MultiPlatformExporter
+ from aividio.platforms.exporter import MultiPlatformExporter
exporter = MultiPlatformExporter()
out_dir = Path(output_dir) if output_dir else Path(video_path).parent / "exports"
@@ -2259,11 +2259,11 @@ def _export_all_platforms(video_path: str, platforms: list[str] | None = None, o
def _register_youtube_tools(self) -> None:
# -- upload_to_youtube ---------------------------------------------
def _upload_to_youtube(video_path: str, title: str, description: str, tags: list[str] | None = None, visibility: str = "private", thumbnail_path: str = "", **kwargs: Any) -> str:
- from vidmation.services.youtube.auth import get_credentials
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.auth import get_credentials
+ from aividio.services.youtube.uploader import YouTubeUploader
- token_path = Path.home() / ".vidmation" / "youtube_token.json"
- secret_path = Path.home() / ".vidmation" / "client_secret.json"
+ token_path = Path.home() / ".aividio" / "youtube_token.json"
+ secret_path = Path.home() / ".aividio" / "client_secret.json"
creds = get_credentials(token_path=token_path, client_secret_path=secret_path)
uploader = YouTubeUploader(credentials=creds)
video_id = uploader.upload(
@@ -2277,11 +2277,11 @@ def _upload_to_youtube(video_path: str, title: str, description: str, tags: list
# -- set_youtube_thumbnail -----------------------------------------
def _set_youtube_thumbnail(video_id: str, thumbnail_path: str, **kwargs: Any) -> str:
- from vidmation.services.youtube.auth import get_credentials
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.auth import get_credentials
+ from aividio.services.youtube.uploader import YouTubeUploader
- token_path = Path.home() / ".vidmation" / "youtube_token.json"
- secret_path = Path.home() / ".vidmation" / "client_secret.json"
+ token_path = Path.home() / ".aividio" / "youtube_token.json"
+ secret_path = Path.home() / ".aividio" / "client_secret.json"
creds = get_credentials(token_path=token_path, client_secret_path=secret_path)
uploader = YouTubeUploader(credentials=creds)
uploader._set_thumbnail(video_id, Path(thumbnail_path))
@@ -2291,7 +2291,7 @@ def _set_youtube_thumbnail(video_id: str, thumbnail_path: str, **kwargs: Any) ->
# -- get_youtube_analytics -----------------------------------------
def _get_youtube_analytics(video_id: str, **kwargs: Any) -> str:
- from vidmation.analytics.youtube_analytics import YouTubeAnalyticsFetcher
+ from aividio.analytics.youtube_analytics import YouTubeAnalyticsFetcher
fetcher = YouTubeAnalyticsFetcher()
stats = fetcher.fetch_video_stats(video_id=video_id)
@@ -2301,7 +2301,7 @@ def _get_youtube_analytics(video_id: str, **kwargs: Any) -> str:
# -- schedule_youtube_publish --------------------------------------
def _schedule_youtube_publish(video_id: str, publish_at: str, **kwargs: Any) -> str:
- from vidmation.publishing.manager import PublishManager
+ from aividio.publishing.manager import PublishManager
pm = PublishManager()
from datetime import datetime
@@ -2319,7 +2319,7 @@ def _schedule_youtube_publish(video_id: str, publish_at: str, **kwargs: Any) ->
def _register_analytics_tools(self) -> None:
# -- estimate_total_cost -------------------------------------------
def _estimate_total_cost(sections: str, **kwargs: Any) -> str:
- from vidmation.services.models.orchestrator import ModelOrchestrator
+ from aividio.services.models.orchestrator import ModelOrchestrator
orch = ModelOrchestrator(settings=self.settings)
sections_list = json.loads(sections)
@@ -2330,7 +2330,7 @@ def _estimate_total_cost(sections: str, **kwargs: Any) -> str:
# -- track_api_usage -----------------------------------------------
def _track_api_usage(service: str, operation: str, cost: float = 0.0, **kwargs: Any) -> str:
- from vidmation.analytics.tracker import get_tracker
+ from aividio.analytics.tracker import get_tracker
tracker = get_tracker()
event = tracker.track(service=service, operation=operation, cost_usd=cost, **kwargs)
@@ -2340,7 +2340,7 @@ def _track_api_usage(service: str, operation: str, cost: float = 0.0, **kwargs:
# -- get_cost_summary ----------------------------------------------
def _get_cost_summary(period: str = "monthly", **kwargs: Any) -> str:
- from vidmation.analytics.reports import ReportGenerator
+ from aividio.analytics.reports import ReportGenerator
gen = ReportGenerator()
report = gen.cost_report(period=period)
@@ -2350,7 +2350,7 @@ def _get_cost_summary(period: str = "monthly", **kwargs: Any) -> str:
# -- get_video_cost ------------------------------------------------
def _get_video_cost(video_id: str, **kwargs: Any) -> str:
- from vidmation.analytics.reports import ReportGenerator
+ from aividio.analytics.reports import ReportGenerator
gen = ReportGenerator()
cost = gen.video_cost_report(video_id=video_id)
@@ -2360,7 +2360,7 @@ def _get_video_cost(video_id: str, **kwargs: Any) -> str:
# -- generate_efficiency_report ------------------------------------
def _generate_efficiency_report(**kwargs: Any) -> str:
- from vidmation.analytics.reports import ReportGenerator
+ from aividio.analytics.reports import ReportGenerator
gen = ReportGenerator()
report = gen.efficiency_report()
@@ -2375,8 +2375,8 @@ def _generate_efficiency_report(**kwargs: Any) -> str:
def _register_db_tools(self) -> None:
# -- create_channel ------------------------------------------------
def _create_channel(name: str, **kwargs: Any) -> str:
- from vidmation.db.engine import get_session
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import ChannelRepo
session = get_session()
try:
@@ -2390,8 +2390,8 @@ def _create_channel(name: str, **kwargs: Any) -> str:
# -- list_channels -------------------------------------------------
def _list_channels(**kwargs: Any) -> str:
- from vidmation.db.engine import get_session
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import ChannelRepo
session = get_session()
try:
@@ -2405,8 +2405,8 @@ def _list_channels(**kwargs: Any) -> str:
# -- create_video_record -------------------------------------------
def _create_video_record(topic: str, channel_id: str = "", **kwargs: Any) -> str:
- from vidmation.db.engine import get_session
- from vidmation.db.repos import VideoRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import VideoRepo
session = get_session()
try:
@@ -2420,9 +2420,9 @@ def _create_video_record(topic: str, channel_id: str = "", **kwargs: Any) -> str
# -- update_video_status -------------------------------------------
def _update_video_status(video_id: str, status: str, **kwargs: Any) -> str:
- from vidmation.db.engine import get_session
- from vidmation.db.repos import VideoRepo
- from vidmation.models.video import VideoStatus
+ from aividio.db.engine import get_session
+ from aividio.db.repos import VideoRepo
+ from aividio.models.video import VideoStatus
session = get_session()
try:
@@ -2439,8 +2439,8 @@ def _update_video_status(video_id: str, status: str, **kwargs: Any) -> str:
# -- create_job ----------------------------------------------------
def _create_job(video_id: str, job_type: str = "full_pipeline", **kwargs: Any) -> str:
- from vidmation.db.engine import get_session
- from vidmation.db.repos import JobRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import JobRepo
session = get_session()
try:
@@ -2454,8 +2454,8 @@ def _create_job(video_id: str, job_type: str = "full_pipeline", **kwargs: Any) -
# -- list_recent_videos --------------------------------------------
def _list_recent_videos(limit: int = 20, **kwargs: Any) -> str:
- from vidmation.db.engine import get_session
- from vidmation.db.repos import VideoRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import VideoRepo
session = get_session()
try:
@@ -2474,7 +2474,7 @@ def _list_recent_videos(limit: int = 20, **kwargs: Any) -> str:
def _register_notification_tools(self) -> None:
# -- send_notification ---------------------------------------------
def _send_notification(event: str, title: str, message: str, **kwargs: Any) -> str:
- from vidmation.notifications.manager import NotificationManager
+ from aividio.notifications.manager import NotificationManager
mgr = NotificationManager()
notif = mgr.notify(event=event, title=title, message=message, data=kwargs.get("data"))
@@ -2486,7 +2486,7 @@ def _send_notification(event: str, title: str, message: str, **kwargs: Any) -> s
def _schedule_video(video_id: str, publish_at: str, platforms: list[str] | None = None, **kwargs: Any) -> str:
from datetime import datetime
- from vidmation.publishing.manager import PublishManager
+ from aividio.publishing.manager import PublishManager
pm = PublishManager()
dt = datetime.fromisoformat(publish_at)
@@ -2498,7 +2498,7 @@ def _schedule_video(video_id: str, publish_at: str, platforms: list[str] | None
# -- create_recurring_schedule -------------------------------------
def _create_recurring_schedule(channel_id: str, cron: str, topic_source: str = "ai", **kwargs: Any) -> str:
- from vidmation.scheduling.advanced import AdvancedScheduler
+ from aividio.scheduling.advanced import AdvancedScheduler
scheduler = AdvancedScheduler()
result = scheduler.create_recurring(channel_id=channel_id, cron_expression=cron, topic_source=topic_source)
@@ -2508,7 +2508,7 @@ def _create_recurring_schedule(channel_id: str, cron: str, topic_source: str = "
# -- get_upcoming_schedule -----------------------------------------
def _get_upcoming_schedule(**kwargs: Any) -> str:
- from vidmation.scheduling.advanced import AdvancedScheduler
+ from aividio.scheduling.advanced import AdvancedScheduler
scheduler = AdvancedScheduler()
upcoming = scheduler.get_upcoming(limit=20)
@@ -2525,7 +2525,7 @@ def _register_file_tools(self) -> None:
def _check_ffmpeg(**kwargs: Any) -> str:
import shutil
- from vidmation.utils.ffmpeg import check_ffmpeg_installed
+ from aividio.utils.ffmpeg import check_ffmpeg_installed
ok = check_ffmpeg_installed()
version = "unknown"
@@ -2545,7 +2545,7 @@ def _check_ffmpeg(**kwargs: Any) -> str:
# -- get_audio_duration --------------------------------------------
def _get_audio_duration(path: str, **kwargs: Any) -> str:
- from vidmation.utils.ffmpeg import get_duration
+ from aividio.utils.ffmpeg import get_duration
duration = get_duration(Path(path))
return json.dumps({"path": path, "duration_seconds": duration})
@@ -2554,7 +2554,7 @@ def _get_audio_duration(path: str, **kwargs: Any) -> str:
# -- get_video_resolution ------------------------------------------
def _get_video_resolution(path: str, **kwargs: Any) -> str:
- from vidmation.utils.ffmpeg import get_resolution
+ from aividio.utils.ffmpeg import get_resolution
width, height = get_resolution(Path(path))
return json.dumps({"path": path, "width": width, "height": height})
@@ -2563,7 +2563,7 @@ def _get_video_resolution(path: str, **kwargs: Any) -> str:
# -- create_work_directory -----------------------------------------
def _create_work_directory(video_id: str, **kwargs: Any) -> str:
- from vidmation.utils.files import get_work_dir
+ from aividio.utils.files import get_work_dir
work_dir = get_work_dir(video_id)
return json.dumps({"path": str(work_dir), "video_id": video_id})
diff --git a/src/vidmation/agent/tools.py b/src/aividio/agent/tools.py
similarity index 95%
rename from src/vidmation/agent/tools.py
rename to src/aividio/agent/tools.py
index 4114226..33c5e46 100644
--- a/src/vidmation/agent/tools.py
+++ b/src/aividio/agent/tools.py
@@ -1,6 +1,6 @@
"""Tool implementations for the AI agent orchestrator.
-Each tool wraps an existing VIDMATION service, providing a clean interface
+Each tool wraps an existing AIVIDIO service, providing a clean interface
for the Claude agent to call. Tools handle errors gracefully and return
descriptive results that the agent can reason about.
"""
@@ -13,10 +13,10 @@
from pathlib import Path
from typing import Any
-from vidmation.analytics.tracker import UsageTracker, get_tracker
-from vidmation.config.profiles import ChannelProfile
-from vidmation.config.settings import Settings
-from vidmation.pipeline.context import PipelineContext
+from aividio.analytics.tracker import UsageTracker, get_tracker
+from aividio.config.profiles import ChannelProfile
+from aividio.config.settings import Settings
+from aividio.pipeline.context import PipelineContext
logger = logging.getLogger(__name__)
@@ -84,7 +84,7 @@ def generate_script(
) -> str:
"""Generate a complete video script using Claude."""
try:
- from vidmation.services.scriptgen import create_script_generator
+ from aividio.services.scriptgen import create_script_generator
generator = create_script_generator(settings=self.settings)
script = generator.generate(topic=topic, profile=self.profile)
@@ -118,7 +118,7 @@ def generate_script(
def generate_voiceover(self, provider: str = "elevenlabs") -> str:
"""Generate TTS voiceover from script."""
try:
- from vidmation.services.tts import create_tts_provider
+ from aividio.services.tts import create_tts_provider
if self.ctx.script is None:
return "ERROR: No script available. Run generate_script first."
@@ -168,7 +168,7 @@ def generate_voiceover(self, provider: str = "elevenlabs") -> str:
def transcribe_audio(self) -> str:
"""Get word-level timestamps from voiceover using Whisper."""
try:
- from vidmation.services.captions.whisper import WhisperCaptionGenerator
+ from aividio.services.captions.whisper import WhisperCaptionGenerator
if self.ctx.voiceover_path is None:
return "ERROR: No voiceover available. Run generate_voiceover first."
@@ -208,7 +208,7 @@ def search_stock_media(
) -> str:
"""Search and download stock media from Pexels/Pixabay."""
try:
- from vidmation.services.media import create_media_provider
+ from aividio.services.media import create_media_provider
media_provider = create_media_provider(settings=self.settings)
output_dir = self.ctx.work_dir / "media"
@@ -296,7 +296,7 @@ def generate_ai_video(
) -> str:
"""Generate AI video clip using ModelOrchestrator."""
try:
- from vidmation.services.models.orchestrator import ModelOrchestrator
+ from aividio.services.models.orchestrator import ModelOrchestrator
orchestrator = ModelOrchestrator(settings=self.settings)
output_dir = self.ctx.work_dir / "ai_clips"
@@ -353,7 +353,7 @@ def generate_ai_image(
) -> str:
"""Generate an AI image."""
try:
- from vidmation.services.imagegen import create_image_generator
+ from aividio.services.imagegen import create_image_generator
generator = create_image_generator(provider=provider, settings=self.settings)
output_path = self.ctx.work_dir / "media" / f"ai_image_{section_index:03d}.png"
@@ -382,8 +382,8 @@ def generate_ai_image(
def assemble_video(self) -> str:
"""Assemble all components into final video."""
try:
- from vidmation.utils.files import get_output_path, get_work_dir
- from vidmation.video.assembler import VideoAssembler
+ from aividio.utils.files import get_output_path, get_work_dir
+ from aividio.video.assembler import VideoAssembler
if self.ctx.voiceover_path is None:
return "ERROR: No voiceover available. Run generate_voiceover first."
@@ -427,7 +427,7 @@ def apply_captions(self, template: str = "auto") -> str:
If template is "auto", selects based on the channel niche.
"""
try:
- from vidmation.captions.templates import get_template
+ from aividio.captions.templates import get_template
if self.ctx.final_video_path is None:
return "ERROR: No video to apply captions to. Run assemble_video first."
@@ -447,7 +447,7 @@ def apply_captions(self, template: str = "auto") -> str:
template = "bold_centered"
caption_template = get_template(template)
- from vidmation.video.captions_render import burn_captions, generate_ass_file
+ from aividio.video.captions_render import burn_captions, generate_ass_file
ass_path = self.ctx.work_dir / "captions.ass"
generate_ass_file(
@@ -481,7 +481,7 @@ def apply_captions(self, template: str = "auto") -> str:
def apply_magic_zoom(self, style: str = "smooth", max_zooms: int = 8) -> str:
"""Add auto-zoom effects at emphasis points."""
try:
- from vidmation.effects.magic_zoom import MagicZoom
+ from aividio.effects.magic_zoom import MagicZoom
if self.ctx.final_video_path is None:
return "ERROR: No video available."
@@ -528,7 +528,7 @@ def apply_magic_zoom(self, style: str = "smooth", max_zooms: int = 8) -> str:
def remove_silence(self, mode: str = "normal") -> str:
"""Remove dead air and filler words."""
try:
- from vidmation.effects.silence_remover import SilenceRemover
+ from aividio.effects.silence_remover import SilenceRemover
if self.ctx.final_video_path is None:
return "ERROR: No video available."
@@ -567,7 +567,7 @@ def remove_silence(self, mode: str = "normal") -> str:
def add_broll(self, max_clips: int = 6) -> str:
"""Insert contextual B-roll footage."""
try:
- from vidmation.effects.magic_broll import MagicBRoll
+ from aividio.effects.magic_broll import MagicBRoll
if self.ctx.final_video_path is None:
return "ERROR: No video available."
@@ -587,7 +587,7 @@ def add_broll(self, max_clips: int = 6) -> str:
return "No B-roll opportunities identified. Skipping."
# Source B-roll clips from stock providers
- from vidmation.services.media import create_media_provider
+ from aividio.services.media import create_media_provider
media_provider = create_media_provider(settings=self.settings)
broll_dir = self.ctx.work_dir / "broll"
@@ -639,7 +639,7 @@ def add_broll(self, max_clips: int = 6) -> str:
def add_emoji_sfx(self) -> str:
"""Add emoji overlays and sound effects."""
try:
- from vidmation.effects.emoji_sfx import EmojiSFXEngine
+ from aividio.effects.emoji_sfx import EmojiSFXEngine
if self.ctx.final_video_path is None:
return "ERROR: No video available."
@@ -671,7 +671,7 @@ def add_emoji_sfx(self) -> str:
def generate_thumbnail(self, style: str = "auto") -> str:
"""Generate video thumbnail."""
try:
- from vidmation.services.imagegen import create_image_generator
+ from aividio.services.imagegen import create_image_generator
if self.ctx.script is None:
return "ERROR: No script available."
@@ -704,7 +704,7 @@ def generate_thumbnail(self, style: str = "auto") -> str:
def optimize_seo(self) -> str:
"""Optimise title, description, tags for YouTube SEO."""
try:
- from vidmation.seo.optimizer import SEOOptimizer
+ from aividio.seo.optimizer import SEOOptimizer
if self.ctx.script is None:
return "ERROR: No script available."
@@ -753,7 +753,7 @@ def optimize_seo(self) -> str:
def apply_brand_kit(self) -> str:
"""Apply branding (logo, watermark, intro/outro)."""
try:
- from vidmation.brand import add_logo_overlay
+ from aividio.brand import add_logo_overlay
if self.ctx.final_video_path is None:
return "ERROR: No video available."
@@ -795,7 +795,7 @@ def apply_brand_kit(self) -> str:
def export_platforms(self, platforms: list[str] | None = None) -> str:
"""Export for multiple platforms."""
try:
- from vidmation.platforms.exporter import MultiPlatformExporter
+ from aividio.platforms.exporter import MultiPlatformExporter
if self.ctx.final_video_path is None:
return "ERROR: No video available."
@@ -831,7 +831,7 @@ def extract_clips(
) -> str:
"""Extract viral short-form clips from the video."""
try:
- from vidmation.effects.magic_clips import MagicClips
+ from aividio.effects.magic_clips import MagicClips
if self.ctx.final_video_path is None:
return "ERROR: No video available."
@@ -885,8 +885,8 @@ def extract_clips(
def upload_youtube(self, visibility: str = "private") -> str:
"""Upload to YouTube."""
try:
- from vidmation.services.youtube.auth import get_credentials
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.auth import get_credentials
+ from aividio.services.youtube.uploader import YouTubeUploader
if self.ctx.final_video_path is None:
return "ERROR: No video available."
diff --git a/src/aividio/analytics/__init__.py b/src/aividio/analytics/__init__.py
new file mode 100644
index 0000000..ba58632
--- /dev/null
+++ b/src/aividio/analytics/__init__.py
@@ -0,0 +1,12 @@
+"""Analytics module - usage tracking, cost monitoring, and reporting."""
+
+from aividio.analytics.reports import ReportGenerator
+from aividio.analytics.tracker import UsageTracker, get_tracker
+from aividio.analytics.youtube_analytics import YouTubeAnalyticsFetcher
+
+__all__ = [
+ "UsageTracker",
+ "get_tracker",
+ "YouTubeAnalyticsFetcher",
+ "ReportGenerator",
+]
diff --git a/src/vidmation/analytics/reports.py b/src/aividio/analytics/reports.py
similarity index 98%
rename from src/vidmation/analytics/reports.py
rename to src/aividio/analytics/reports.py
index b26b1bd..4c3462a 100644
--- a/src/vidmation/analytics/reports.py
+++ b/src/aividio/analytics/reports.py
@@ -9,11 +9,11 @@
from sqlalchemy import func, select
-from vidmation.db.engine import get_session
-from vidmation.models.analytics import UsageEvent, VideoAnalytics
-from vidmation.models.video import Video, VideoStatus
+from aividio.db.engine import get_session
+from aividio.models.analytics import UsageEvent, VideoAnalytics
+from aividio.models.video import Video, VideoStatus
-logger = logging.getLogger("vidmation.analytics.reports")
+logger = logging.getLogger("aividio.analytics.reports")
class ReportGenerator:
diff --git a/src/vidmation/analytics/tracker.py b/src/aividio/analytics/tracker.py
similarity index 99%
rename from src/vidmation/analytics/tracker.py
rename to src/aividio/analytics/tracker.py
index c9841ac..08c9fac 100644
--- a/src/vidmation/analytics/tracker.py
+++ b/src/aividio/analytics/tracker.py
@@ -8,10 +8,10 @@
from sqlalchemy import func, select
-from vidmation.db.engine import get_session
-from vidmation.models.analytics import CostSummary, UsageEvent
+from aividio.db.engine import get_session
+from aividio.models.analytics import CostSummary, UsageEvent
-logger = logging.getLogger("vidmation.analytics.tracker")
+logger = logging.getLogger("aividio.analytics.tracker")
_tracker_instance: UsageTracker | None = None
diff --git a/src/vidmation/analytics/youtube_analytics.py b/src/aividio/analytics/youtube_analytics.py
similarity index 97%
rename from src/vidmation/analytics/youtube_analytics.py
rename to src/aividio/analytics/youtube_analytics.py
index c8dba91..c902f0a 100644
--- a/src/vidmation/analytics/youtube_analytics.py
+++ b/src/aividio/analytics/youtube_analytics.py
@@ -9,12 +9,12 @@
from sqlalchemy import select
-from vidmation.db.engine import get_session
-from vidmation.models.analytics import VideoAnalytics
-from vidmation.models.channel import Channel
-from vidmation.models.video import Video, VideoStatus
+from aividio.db.engine import get_session
+from aividio.models.analytics import VideoAnalytics
+from aividio.models.channel import Channel
+from aividio.models.video import Video, VideoStatus
-logger = logging.getLogger("vidmation.analytics.youtube")
+logger = logging.getLogger("aividio.analytics.youtube")
class YouTubeAnalyticsFetcher:
diff --git a/src/aividio/api/__init__.py b/src/aividio/api/__init__.py
new file mode 100644
index 0000000..de60d0d
--- /dev/null
+++ b/src/aividio/api/__init__.py
@@ -0,0 +1 @@
+"""AIVIDIO public REST API — authentication, webhooks, and versioned routes."""
diff --git a/src/vidmation/api/auth.py b/src/aividio/api/auth.py
similarity index 98%
rename from src/vidmation/api/auth.py
rename to src/aividio/api/auth.py
index e671f1e..fe61c87 100644
--- a/src/vidmation/api/auth.py
+++ b/src/aividio/api/auth.py
@@ -15,8 +15,8 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
-from vidmation.db.engine import get_session
-from vidmation.models.api_key import APIKey
+from aividio.db.engine import get_session
+from aividio.models.api_key import APIKey
logger = logging.getLogger(__name__)
diff --git a/src/aividio/api/v1/__init__.py b/src/aividio/api/v1/__init__.py
new file mode 100644
index 0000000..9ea6c17
--- /dev/null
+++ b/src/aividio/api/v1/__init__.py
@@ -0,0 +1 @@
+"""AIVIDIO API v1 — versioned endpoint package."""
diff --git a/src/vidmation/api/v1/agent.py b/src/aividio/api/v1/agent.py
similarity index 94%
rename from src/vidmation/api/v1/agent.py
rename to src/aividio/api/v1/agent.py
index bea3f42..3671600 100644
--- a/src/vidmation/api/v1/agent.py
+++ b/src/aividio/api/v1/agent.py
@@ -9,10 +9,10 @@
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from pydantic import BaseModel, Field
-from vidmation.auth.dependencies import require_active_user
-from vidmation.config.profiles import ChannelProfile, get_default_profile, load_profile
-from vidmation.config.settings import get_settings
-from vidmation.models.user import User
+from aividio.auth.dependencies import require_active_user
+from aividio.config.profiles import ChannelProfile, get_default_profile, load_profile
+from aividio.config.settings import get_settings
+from aividio.models.user import User
logger = logging.getLogger(__name__)
@@ -106,7 +106,7 @@ def _run_agent_create(
budget_limit: float | None,
) -> None:
"""Background task that runs the agent orchestrator."""
- from vidmation.agent.orchestrator import AgentOrchestrator
+ from aividio.agent.orchestrator import AgentOrchestrator
_agent_jobs[job_id]["status"] = "running"
@@ -220,7 +220,7 @@ async def plan_video(
Synchronous -- returns the plan text directly.
"""
- from vidmation.agent.orchestrator import AgentOrchestrator
+ from aividio.agent.orchestrator import AgentOrchestrator
settings = get_settings()
profile = _resolve_profile(request.channel)
@@ -246,8 +246,8 @@ async def review_video(
import json
from pathlib import Path
- from vidmation.agent.orchestrator import AgentOrchestrator
- from vidmation.pipeline.context import PipelineContext
+ from aividio.agent.orchestrator import AgentOrchestrator
+ from aividio.pipeline.context import PipelineContext
settings = get_settings()
context_path = settings.output_dir / video_id / "pipeline_context.json"
diff --git a/src/vidmation/api/v1/assets.py b/src/aividio/api/v1/assets.py
similarity index 97%
rename from src/vidmation/api/v1/assets.py
rename to src/aividio/api/v1/assets.py
index 1d0537a..deddb11 100644
--- a/src/vidmation/api/v1/assets.py
+++ b/src/aividio/api/v1/assets.py
@@ -14,9 +14,9 @@
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile, status
from pydantic import BaseModel, ConfigDict
-from vidmation.auth.dependencies import optional_user, require_active_user
-from vidmation.models.user import User
-from vidmation.services.assets.manager import UPLOADABLE_TYPES, AssetManager
+from aividio.auth.dependencies import optional_user, require_active_user
+from aividio.models.user import User
+from aividio.services.assets.manager import UPLOADABLE_TYPES, AssetManager
router = APIRouter(prefix="/assets", tags=["assets"])
diff --git a/src/vidmation/api/v1/billing.py b/src/aividio/api/v1/billing.py
similarity index 95%
rename from src/vidmation/api/v1/billing.py
rename to src/aividio/api/v1/billing.py
index 85271d1..fccea96 100644
--- a/src/vidmation/api/v1/billing.py
+++ b/src/aividio/api/v1/billing.py
@@ -8,11 +8,11 @@
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
-from vidmation.auth.dependencies import require_active_user
-from vidmation.billing.plans import get_plan
-from vidmation.billing.stripe_service import StripeService
-from vidmation.config.settings import get_settings
-from vidmation.models.user import User
+from aividio.auth.dependencies import require_active_user
+from aividio.billing.plans import get_plan
+from aividio.billing.stripe_service import StripeService
+from aividio.config.settings import get_settings
+from aividio.models.user import User
logger = logging.getLogger(__name__)
@@ -76,7 +76,7 @@ def _price_id_for(plan: str, interval: str) -> str:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"No Stripe price configured for {plan}/{interval}. "
- f"Set VIDMATION_{key.upper()} in your environment.",
+ f"Set AIVIDIO_{key.upper()} in your environment.",
)
return price_id
diff --git a/src/vidmation/api/v1/channels.py b/src/aividio/api/v1/channels.py
similarity index 97%
rename from src/vidmation/api/v1/channels.py
rename to src/aividio/api/v1/channels.py
index 352f215..093cc48 100644
--- a/src/vidmation/api/v1/channels.py
+++ b/src/aividio/api/v1/channels.py
@@ -10,7 +10,7 @@
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func, select
-from vidmation.api.v1.schemas import (
+from aividio.api.v1.schemas import (
ChannelCreateRequest,
ChannelResponse,
ChannelUpdateRequest,
@@ -18,10 +18,10 @@
PaginatedResponse,
YouTubeConnectResponse,
)
-from vidmation.auth.dependencies import require_active_user
-from vidmation.db.engine import get_session
-from vidmation.models.channel import Channel
-from vidmation.models.user import User
+from aividio.auth.dependencies import require_active_user
+from aividio.db.engine import get_session
+from aividio.models.channel import Channel
+from aividio.models.user import User
logger = logging.getLogger(__name__)
@@ -216,7 +216,7 @@ async def connect_youtube(
try:
from google_auth_oauthlib.flow import Flow
- from vidmation.config.settings import get_settings
+ from aividio.config.settings import get_settings
settings = get_settings()
diff --git a/src/vidmation/api/v1/generate.py b/src/aividio/api/v1/generate.py
similarity index 92%
rename from src/vidmation/api/v1/generate.py
rename to src/aividio/api/v1/generate.py
index 3ec392a..fd958d4 100644
--- a/src/vidmation/api/v1/generate.py
+++ b/src/aividio/api/v1/generate.py
@@ -10,7 +10,7 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
-from vidmation.api.v1.schemas import (
+from aividio.api.v1.schemas import (
ErrorResponse,
GenerateScriptRequest,
GenerateThumbnailRequest,
@@ -21,14 +21,14 @@
ThumbnailResponse,
VoiceoverResponse,
)
-from vidmation.api.webhooks import WebhookManager
-from vidmation.auth.dependencies import require_active_user
-from vidmation.db.engine import get_session
-from vidmation.db.repos import JobRepo, VideoRepo
-from vidmation.models.channel import Channel
-from vidmation.models.job import JobStatus, JobType
-from vidmation.models.user import User
-from vidmation.models.video import VideoFormat, VideoStatus
+from aividio.api.webhooks import WebhookManager
+from aividio.auth.dependencies import require_active_user
+from aividio.db.engine import get_session
+from aividio.db.repos import JobRepo, VideoRepo
+from aividio.models.channel import Channel
+from aividio.models.job import JobStatus, JobType
+from aividio.models.user import User
+from aividio.models.video import VideoFormat, VideoStatus
logger = logging.getLogger(__name__)
@@ -56,7 +56,7 @@ async def generate_script(
``POST /api/v1/videos`` which runs the full pipeline asynchronously.
"""
try:
- from vidmation.services.script import ScriptGenerator
+ from aividio.services.script import ScriptGenerator
except ImportError:
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
@@ -76,7 +76,7 @@ async def generate_script(
channel = session.scalars(stmt).first()
if channel:
try:
- from vidmation.config.profiles import load_profile
+ from aividio.config.profiles import load_profile
profile = load_profile(channel.profile_path)
except Exception:
@@ -129,7 +129,7 @@ async def generate_voiceover(
):
"""Generate a voiceover audio file from text using TTS."""
try:
- from vidmation.services.tts import TTSService
+ from aividio.services.tts import TTSService
except ImportError:
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
@@ -176,7 +176,7 @@ async def generate_thumbnail(
):
"""Generate a thumbnail image from a text prompt."""
try:
- from vidmation.services.image import ImageGenerator
+ from aividio.services.image import ImageGenerator
except ImportError:
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
diff --git a/src/vidmation/api/v1/jobs.py b/src/aividio/api/v1/jobs.py
similarity index 96%
rename from src/vidmation/api/v1/jobs.py
rename to src/aividio/api/v1/jobs.py
index b65d20c..aab2011 100644
--- a/src/vidmation/api/v1/jobs.py
+++ b/src/aividio/api/v1/jobs.py
@@ -10,7 +10,7 @@
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func, select
-from vidmation.api.v1.schemas import (
+from aividio.api.v1.schemas import (
ErrorResponse,
JobListResponse,
JobLogEntry,
@@ -18,12 +18,12 @@
JobProgressResponse,
JobResponse,
)
-from vidmation.auth.dependencies import require_active_user
-from vidmation.db.engine import get_session
-from vidmation.db.repos import JobRepo, VideoRepo
-from vidmation.models.job import Job, JobStatus, JobType
-from vidmation.models.user import User
-from vidmation.models.video import Video, VideoStatus
+from aividio.auth.dependencies import require_active_user
+from aividio.db.engine import get_session
+from aividio.db.repos import JobRepo, VideoRepo
+from aividio.models.job import Job, JobStatus, JobType
+from aividio.models.user import User
+from aividio.models.video import Video, VideoStatus
router = APIRouter(prefix="/jobs", tags=["jobs"])
diff --git a/src/vidmation/api/v1/publish.py b/src/aividio/api/v1/publish.py
similarity index 95%
rename from src/vidmation/api/v1/publish.py
rename to src/aividio/api/v1/publish.py
index 8339992..0865490 100644
--- a/src/vidmation/api/v1/publish.py
+++ b/src/aividio/api/v1/publish.py
@@ -13,18 +13,18 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
-from vidmation.api.v1.schemas import (
+from aividio.api.v1.schemas import (
ErrorResponse,
PublishRequest,
PublishResponse,
)
-from vidmation.auth.dependencies import require_active_user
-from vidmation.db.engine import get_session
-from vidmation.db.repos import JobRepo, VideoRepo
-from vidmation.models.channel import Channel
-from vidmation.models.job import JobStatus, JobType
-from vidmation.models.user import User
-from vidmation.models.video import Video, VideoStatus
+from aividio.auth.dependencies import require_active_user
+from aividio.db.engine import get_session
+from aividio.db.repos import JobRepo, VideoRepo
+from aividio.models.channel import Channel
+from aividio.models.job import JobStatus, JobType
+from aividio.models.user import User
+from aividio.models.video import Video, VideoStatus
logger = logging.getLogger(__name__)
@@ -141,7 +141,7 @@ async def publish_video(
publish_status = "queued"
if scheduled_at:
try:
- from vidmation.models.schedule import Schedule, ScheduleStatus, ScheduleType
+ from aividio.models.schedule import Schedule, ScheduleStatus, ScheduleType
schedule_record = Schedule(
channel_id=channel.id,
@@ -167,7 +167,7 @@ async def publish_video(
# --- Fire webhook ---
try:
- from vidmation.api.webhooks import WebhookManager
+ from aividio.api.webhooks import WebhookManager
wh = WebhookManager()
wh.fire_sync(
diff --git a/src/vidmation/api/v1/router.py b/src/aividio/api/v1/router.py
similarity index 55%
rename from src/vidmation/api/v1/router.py
rename to src/aividio/api/v1/router.py
index e52f38a..81101dc 100644
--- a/src/vidmation/api/v1/router.py
+++ b/src/aividio/api/v1/router.py
@@ -4,20 +4,22 @@
from fastapi import APIRouter
-from vidmation.api.v1.agent import router as agent_router
-from vidmation.api.v1.assets import router as assets_router
-from vidmation.api.v1.channels import router as channels_router
-from vidmation.api.v1.generate import router as generate_router
-from vidmation.api.v1.jobs import router as jobs_router
-from vidmation.api.v1.publish import router as publish_router
-from vidmation.api.v1.videos import router as videos_router
-from vidmation.api.v1.webhooks_routes import router as webhooks_router
+from aividio.api.v1.agent import router as agent_router
+from aividio.api.v1.assets import router as assets_router
+from aividio.api.v1.billing import router as billing_router
+from aividio.api.v1.channels import router as channels_router
+from aividio.api.v1.generate import router as generate_router
+from aividio.api.v1.jobs import router as jobs_router
+from aividio.api.v1.publish import router as publish_router
+from aividio.api.v1.videos import router as videos_router
+from aividio.api.v1.webhooks_routes import router as webhooks_router
router = APIRouter()
# Each sub-router already carries its own prefix (/videos, /channels, etc.)
router.include_router(agent_router)
router.include_router(assets_router)
+router.include_router(billing_router)
router.include_router(videos_router)
router.include_router(channels_router)
router.include_router(jobs_router)
diff --git a/src/vidmation/api/v1/schemas.py b/src/aividio/api/v1/schemas.py
similarity index 99%
rename from src/vidmation/api/v1/schemas.py
rename to src/aividio/api/v1/schemas.py
index 2f641c2..7d45278 100644
--- a/src/vidmation/api/v1/schemas.py
+++ b/src/aividio/api/v1/schemas.py
@@ -1,4 +1,4 @@
-"""Pydantic v2 request/response schemas for the VIDMATION public API."""
+"""Pydantic v2 request/response schemas for the AIVIDIO public API."""
from __future__ import annotations
diff --git a/src/vidmation/api/v1/videos.py b/src/aividio/api/v1/videos.py
similarity index 96%
rename from src/vidmation/api/v1/videos.py
rename to src/aividio/api/v1/videos.py
index ae0b461..93c4a1c 100644
--- a/src/vidmation/api/v1/videos.py
+++ b/src/aividio/api/v1/videos.py
@@ -11,7 +11,7 @@
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func, select
-from vidmation.api.v1.schemas import (
+from aividio.api.v1.schemas import (
BatchCreateRequest,
BatchItemResponse,
BatchResponse,
@@ -23,14 +23,14 @@
VideoResponse,
VideoStatusResponse,
)
-from vidmation.api.webhooks import WebhookManager
-from vidmation.auth.dependencies import require_active_user
-from vidmation.db.engine import get_session
-from vidmation.db.repos import JobRepo, VideoRepo
-from vidmation.models.channel import Channel
-from vidmation.models.job import Job, JobStatus, JobType
-from vidmation.models.user import User
-from vidmation.models.video import Video, VideoFormat, VideoStatus
+from aividio.api.webhooks import WebhookManager
+from aividio.auth.dependencies import require_active_user
+from aividio.db.engine import get_session
+from aividio.db.repos import JobRepo, VideoRepo
+from aividio.models.channel import Channel
+from aividio.models.job import Job, JobStatus, JobType
+from aividio.models.user import User
+from aividio.models.video import Video, VideoFormat, VideoStatus
router = APIRouter(prefix="/videos", tags=["videos"])
diff --git a/src/vidmation/api/v1/webhooks_routes.py b/src/aividio/api/v1/webhooks_routes.py
similarity index 93%
rename from src/vidmation/api/v1/webhooks_routes.py
rename to src/aividio/api/v1/webhooks_routes.py
index 98d1d2e..903bfba 100644
--- a/src/vidmation/api/v1/webhooks_routes.py
+++ b/src/aividio/api/v1/webhooks_routes.py
@@ -7,16 +7,16 @@
from fastapi import APIRouter, Depends, HTTPException, status
-from vidmation.api.v1.schemas import (
+from aividio.api.v1.schemas import (
ErrorResponse,
WebhookCreateRequest,
WebhookResponse,
WebhookTestResponse,
)
-from vidmation.api.webhooks import WebhookManager
-from vidmation.auth.dependencies import require_active_user
-from vidmation.db.engine import get_session
-from vidmation.models.user import User
+from aividio.api.webhooks import WebhookManager
+from aividio.auth.dependencies import require_active_user
+from aividio.db.engine import get_session
+from aividio.models.user import User
router = APIRouter(prefix="/webhooks", tags=["webhooks"])
diff --git a/src/vidmation/api/webhooks.py b/src/aividio/api/webhooks.py
similarity index 97%
rename from src/vidmation/api/webhooks.py
rename to src/aividio/api/webhooks.py
index 690cf7b..2afb865 100644
--- a/src/vidmation/api/webhooks.py
+++ b/src/aividio/api/webhooks.py
@@ -14,8 +14,8 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
-from vidmation.db.engine import get_session
-from vidmation.models.webhook import Webhook
+from aividio.db.engine import get_session
+from aividio.models.webhook import Webhook
logger = logging.getLogger(__name__)
@@ -124,7 +124,7 @@ async def _deliver(self, webhook: Webhook, body: str, body_bytes: bytes) -> None
"""Deliver a single webhook with retry logic."""
headers: dict[str, str] = {
"Content-Type": "application/json",
- "User-Agent": "VIDMATION-Webhooks/1.0",
+ "User-Agent": "AIVIDIO-Webhooks/1.0",
}
if webhook.secret:
sig = _sign_payload(body_bytes, webhook.secret)
@@ -195,7 +195,7 @@ def test_webhook(self, webhook_id: str) -> dict:
"event": "webhook.test",
"delivery_id": str(uuid.uuid4()),
"timestamp": datetime.now(timezone.utc).isoformat(),
- "data": {"message": "This is a test delivery from VIDMATION."},
+ "data": {"message": "This is a test delivery from AIVIDIO."},
},
default=str,
)
@@ -203,7 +203,7 @@ def test_webhook(self, webhook_id: str) -> dict:
headers: dict[str, str] = {
"Content-Type": "application/json",
- "User-Agent": "VIDMATION-Webhooks/1.0",
+ "User-Agent": "AIVIDIO-Webhooks/1.0",
}
if webhook.secret:
sig = _sign_payload(body_bytes, webhook.secret)
diff --git a/src/vidmation/audio_first/__init__.py b/src/aividio/audio_first/__init__.py
similarity index 51%
rename from src/vidmation/audio_first/__init__.py
rename to src/aividio/audio_first/__init__.py
index b8cf1ac..679c996 100644
--- a/src/vidmation/audio_first/__init__.py
+++ b/src/aividio/audio_first/__init__.py
@@ -1,6 +1,6 @@
"""Audio-first pipeline — generate video from existing audio content."""
-from vidmation.audio_first.pipeline import AudioFirstPipeline
-from vidmation.audio_first.segmenter import AudioSegmenter
+from aividio.audio_first.pipeline import AudioFirstPipeline
+from aividio.audio_first.segmenter import AudioSegmenter
__all__ = ["AudioFirstPipeline", "AudioSegmenter"]
diff --git a/src/vidmation/audio_first/pipeline.py b/src/aividio/audio_first/pipeline.py
similarity index 96%
rename from src/vidmation/audio_first/pipeline.py
rename to src/aividio/audio_first/pipeline.py
index 7a6047d..85669f5 100644
--- a/src/vidmation/audio_first/pipeline.py
+++ b/src/aividio/audio_first/pipeline.py
@@ -24,11 +24,11 @@
import anthropic
-from vidmation.audio_first.segmenter import AudioSegmenter
-from vidmation.config.profiles import ChannelProfile, get_default_profile, load_profile
-from vidmation.config.settings import Settings, get_settings
-from vidmation.services.captions.whisper import WhisperCaptionGenerator
-from vidmation.utils.retry import retry
+from aividio.audio_first.segmenter import AudioSegmenter
+from aividio.config.profiles import ChannelProfile, get_default_profile, load_profile
+from aividio.config.settings import Settings, get_settings
+from aividio.services.captions.whisper import WhisperCaptionGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
pass
@@ -116,7 +116,7 @@ class AudioFirstPipeline:
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
- self.logger = logging.getLogger("vidmation.audio_first.AudioFirstPipeline")
+ self.logger = logging.getLogger("aividio.audio_first.AudioFirstPipeline")
self._segmenter = AudioSegmenter(settings=self.settings)
# ------------------------------------------------------------------
@@ -129,7 +129,7 @@ def _get_claude_client(self) -> anthropic.Anthropic:
if not api_key:
raise ValueError(
"anthropic_api_key is required for audio analysis. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
return anthropic.Anthropic(api_key=api_key)
@@ -154,8 +154,8 @@ def _call_claude(self, system: str, user_message: str) -> str:
def _resolve_profile(self, channel_name: str) -> ChannelProfile:
"""Load the channel profile, falling back to defaults."""
- from vidmation.db.engine import get_session, init_db
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session, init_db
+ from aividio.db.repos import ChannelRepo
init_db()
session = get_session()
diff --git a/src/vidmation/audio_first/segmenter.py b/src/aividio/audio_first/segmenter.py
similarity index 99%
rename from src/vidmation/audio_first/segmenter.py
rename to src/aividio/audio_first/segmenter.py
index d6aba98..19d7dde 100644
--- a/src/vidmation/audio_first/segmenter.py
+++ b/src/aividio/audio_first/segmenter.py
@@ -13,7 +13,7 @@
from pathlib import Path
from typing import TYPE_CHECKING
-from vidmation.config.settings import Settings, get_settings
+from aividio.config.settings import Settings, get_settings
if TYPE_CHECKING:
pass
@@ -69,7 +69,7 @@ class AudioSegmenter:
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
- self.logger = logging.getLogger("vidmation.audio_first.AudioSegmenter")
+ self.logger = logging.getLogger("aividio.audio_first.AudioSegmenter")
# ------------------------------------------------------------------
# Silence detection
diff --git a/src/vidmation/auth/__init__.py b/src/aividio/auth/__init__.py
similarity index 65%
rename from src/vidmation/auth/__init__.py
rename to src/aividio/auth/__init__.py
index cc2803c..f5f8af5 100644
--- a/src/vidmation/auth/__init__.py
+++ b/src/aividio/auth/__init__.py
@@ -1,13 +1,13 @@
"""Authentication and authorization package for AIVidio."""
-from vidmation.auth.dependencies import (
+from aividio.auth.dependencies import (
get_current_user,
optional_user,
require_active_user,
require_admin,
)
-from vidmation.auth.jwt import create_access_token, create_refresh_token, decode_token
-from vidmation.auth.password import hash_password, verify_password
+from aividio.auth.jwt import create_access_token, create_refresh_token, decode_token
+from aividio.auth.password import hash_password, verify_password
__all__ = [
"create_access_token",
diff --git a/src/vidmation/auth/dependencies.py b/src/aividio/auth/dependencies.py
similarity index 96%
rename from src/vidmation/auth/dependencies.py
rename to src/aividio/auth/dependencies.py
index 78d2e4a..c77b245 100644
--- a/src/vidmation/auth/dependencies.py
+++ b/src/aividio/auth/dependencies.py
@@ -9,9 +9,9 @@
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session
-from vidmation.auth.jwt import TOKEN_TYPE_ACCESS, decode_token
-from vidmation.db.engine import get_session
-from vidmation.models.user import User
+from aividio.auth.jwt import TOKEN_TYPE_ACCESS, decode_token
+from aividio.db.engine import get_session
+from aividio.models.user import User
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/auth/jwt.py b/src/aividio/auth/jwt.py
similarity index 97%
rename from src/vidmation/auth/jwt.py
rename to src/aividio/auth/jwt.py
index db217d6..7478c5c 100644
--- a/src/vidmation/auth/jwt.py
+++ b/src/aividio/auth/jwt.py
@@ -8,7 +8,7 @@
import jwt
-from vidmation.config.settings import get_settings
+from aividio.config.settings import get_settings
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/auth/password.py b/src/aividio/auth/password.py
similarity index 100%
rename from src/vidmation/auth/password.py
rename to src/aividio/auth/password.py
diff --git a/src/vidmation/auth/rate_limit.py b/src/aividio/auth/rate_limit.py
similarity index 100%
rename from src/vidmation/auth/rate_limit.py
rename to src/aividio/auth/rate_limit.py
diff --git a/src/vidmation/auth/routes.py b/src/aividio/auth/routes.py
similarity index 94%
rename from src/vidmation/auth/routes.py
rename to src/aividio/auth/routes.py
index cf32081..3ff6986 100644
--- a/src/vidmation/auth/routes.py
+++ b/src/aividio/auth/routes.py
@@ -11,16 +11,16 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
-from vidmation.auth.dependencies import get_current_user, require_active_user
-from vidmation.auth.jwt import (
+from aividio.auth.dependencies import get_current_user, require_active_user
+from aividio.auth.jwt import (
TOKEN_TYPE_REFRESH,
create_access_token,
create_refresh_token,
decode_token,
)
-from vidmation.auth.password import hash_password, verify_password
-from vidmation.auth.rate_limit import check_auth_rate_limit, check_sensitive_rate_limit
-from vidmation.auth.schemas import (
+from aividio.auth.password import hash_password, verify_password
+from aividio.auth.rate_limit import check_auth_rate_limit, check_sensitive_rate_limit
+from aividio.auth.schemas import (
ChangePasswordRequest,
ForgotPasswordRequest,
LoginRequest,
@@ -32,9 +32,9 @@
UpdateProfileRequest,
UserResponse,
)
-from vidmation.config.settings import get_settings
-from vidmation.db.engine import get_session
-from vidmation.models.user import User
+from aividio.config.settings import get_settings
+from aividio.db.engine import get_session
+from aividio.models.user import User
logger = logging.getLogger(__name__)
@@ -477,16 +477,18 @@ async def forgot_password(body: ForgotPasswordRequest, request: Request):
user.password_reset_expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
db.commit()
- # TODO: Send email via Resend
- # from vidmation.notifications.email import send_password_reset_email
- # await send_password_reset_email(
- # to=user.email,
- # reset_url=f"https://aividio.com/reset-password?token={raw_token}",
- # )
- logger.info(
- "Password reset requested for %s (token generated, email sending not yet implemented)",
- user.email,
- )
+ # Send password reset email via Resend
+ from aividio.notifications.email import send_password_reset_email
+
+ reset_url = f"https://aividio.com/reset-password?token={raw_token}"
+ sent = send_password_reset_email(to=user.email, reset_url=reset_url)
+ if sent:
+ logger.info("Password reset email sent to %s", user.email)
+ else:
+ logger.warning(
+ "Password reset token generated for %s but email delivery failed",
+ user.email,
+ )
# Always return success to prevent user enumeration
return MessageResponse(
diff --git a/src/vidmation/auth/schemas.py b/src/aividio/auth/schemas.py
similarity index 100%
rename from src/vidmation/auth/schemas.py
rename to src/aividio/auth/schemas.py
diff --git a/src/vidmation/batch/__init__.py b/src/aividio/batch/__init__.py
similarity index 67%
rename from src/vidmation/batch/__init__.py
rename to src/aividio/batch/__init__.py
index 09ed635..5dc9de0 100644
--- a/src/vidmation/batch/__init__.py
+++ b/src/aividio/batch/__init__.py
@@ -1,5 +1,5 @@
"""Batch video generation — process multiple topics from lists, CSV, or RSS."""
-from vidmation.batch.generator import BatchVideoGenerator
+from aividio.batch.generator import BatchVideoGenerator
__all__ = ["BatchVideoGenerator"]
diff --git a/src/vidmation/batch/csv_parser.py b/src/aividio/batch/csv_parser.py
similarity index 99%
rename from src/vidmation/batch/csv_parser.py
rename to src/aividio/batch/csv_parser.py
index 71ec99c..9fed13d 100644
--- a/src/vidmation/batch/csv_parser.py
+++ b/src/aividio/batch/csv_parser.py
@@ -56,7 +56,7 @@ class BatchCSVParser:
"""
def __init__(self) -> None:
- self.logger = logging.getLogger("vidmation.batch.CSVParser")
+ self.logger = logging.getLogger("aividio.batch.CSVParser")
def parse(self, csv_path: Path) -> list[BatchRow]:
"""Parse a CSV file and return validated rows.
diff --git a/src/vidmation/batch/generator.py b/src/aividio/batch/generator.py
similarity index 93%
rename from src/vidmation/batch/generator.py
rename to src/aividio/batch/generator.py
index 49b515e..ce3e158 100644
--- a/src/vidmation/batch/generator.py
+++ b/src/aividio/batch/generator.py
@@ -9,15 +9,15 @@
import anthropic
-from vidmation.batch.csv_parser import BatchCSVParser
-from vidmation.config.profiles import ChannelProfile, get_default_profile, load_profile
-from vidmation.config.settings import Settings, get_settings
-from vidmation.db.engine import get_session, init_db
-from vidmation.db.repos import ChannelRepo
-from vidmation.models.job import Job, JobType
-from vidmation.models.video import Video
-from vidmation.queue.tasks import enqueue_video
-from vidmation.utils.retry import retry
+from aividio.batch.csv_parser import BatchCSVParser
+from aividio.config.profiles import ChannelProfile, get_default_profile, load_profile
+from aividio.config.settings import Settings, get_settings
+from aividio.db.engine import get_session, init_db
+from aividio.db.repos import ChannelRepo
+from aividio.models.job import Job, JobType
+from aividio.models.video import Video
+from aividio.queue.tasks import enqueue_video
+from aividio.utils.retry import retry
if TYPE_CHECKING:
pass
@@ -53,12 +53,12 @@ class BatchVideoGenerator:
This is the primary entry point for batch operations. Each method
validates input, resolves the channel, and enqueues individual video
- jobs via :func:`vidmation.queue.tasks.enqueue_video`.
+ jobs via :func:`aividio.queue.tasks.enqueue_video`.
"""
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
- self.logger = logging.getLogger("vidmation.batch.BatchVideoGenerator")
+ self.logger = logging.getLogger("aividio.batch.BatchVideoGenerator")
# ------------------------------------------------------------------
# Channel resolution helpers
@@ -78,7 +78,7 @@ def _resolve_channel(self, channel_name: str) -> Any:
if channel is None:
raise ValueError(
f"Channel '{channel_name}' not found. "
- f"Create it first with: vidmation channel add --name '{channel_name}'"
+ f"Create it first with: aividio channel add --name '{channel_name}'"
)
return channel
finally:
@@ -320,7 +320,7 @@ def generate_topic_ideas(
if not api_key:
raise ValueError(
"anthropic_api_key is required for topic generation. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
client = anthropic.Anthropic(api_key=api_key)
diff --git a/src/vidmation/billing/__init__.py b/src/aividio/billing/__init__.py
similarity index 55%
rename from src/vidmation/billing/__init__.py
rename to src/aividio/billing/__init__.py
index 3811ecd..6baf781 100644
--- a/src/vidmation/billing/__init__.py
+++ b/src/aividio/billing/__init__.py
@@ -1,7 +1,7 @@
"""Stripe billing integration — subscriptions, checkout, webhooks."""
-from vidmation.billing.plans import PLANS, check_video_limit, get_plan, increment_usage
-from vidmation.billing.stripe_service import StripeService
+from aividio.billing.plans import PLANS, check_video_limit, get_plan, increment_usage
+from aividio.billing.stripe_service import StripeService
__all__ = [
"PLANS",
diff --git a/src/vidmation/billing/plans.py b/src/aividio/billing/plans.py
similarity index 96%
rename from src/vidmation/billing/plans.py
rename to src/aividio/billing/plans.py
index 64d6b06..580dcdd 100644
--- a/src/vidmation/billing/plans.py
+++ b/src/aividio/billing/plans.py
@@ -6,8 +6,8 @@
from sqlalchemy.orm import Session
-from vidmation.db.engine import get_session
-from vidmation.models.user import SubscriptionTier, User
+from aividio.db.engine import get_session
+from aividio.models.user import SubscriptionTier, User
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/billing/stripe_service.py b/src/aividio/billing/stripe_service.py
similarity index 94%
rename from src/vidmation/billing/stripe_service.py
rename to src/aividio/billing/stripe_service.py
index 7f3800a..0eb6948 100644
--- a/src/vidmation/billing/stripe_service.py
+++ b/src/aividio/billing/stripe_service.py
@@ -8,10 +8,10 @@
import stripe
from sqlalchemy.orm import Session
-from vidmation.billing.plans import get_plan
-from vidmation.config.settings import get_settings
-from vidmation.db.engine import get_session
-from vidmation.models.user import SubscriptionTier, User
+from aividio.billing.plans import get_plan
+from aividio.config.settings import get_settings
+from aividio.db.engine import get_session
+from aividio.models.user import SubscriptionTier, User
logger = logging.getLogger(__name__)
@@ -24,7 +24,7 @@ def __init__(self) -> None:
key = settings.stripe_secret_key.get_secret_value()
if not key:
raise RuntimeError(
- "VIDMATION_STRIPE_SECRET_KEY is not configured. "
+ "AIVIDIO_STRIPE_SECRET_KEY is not configured. "
"Set it in .env or the environment before using billing features."
)
stripe.api_key = key
@@ -48,7 +48,7 @@ def create_customer(self, user_id: str, email: str) -> str:
customer = stripe.Customer.create(
email=email,
- metadata={"vidmation_user_id": user_id},
+ metadata={"aividio_user_id": user_id},
)
user.stripe_customer_id = customer.id
db.commit()
@@ -90,8 +90,8 @@ def create_checkout_session(
line_items=[{"price": price_id, "quantity": 1}],
success_url=success_url,
cancel_url=cancel_url,
- metadata={"vidmation_user_id": user_id},
- subscription_data={"metadata": {"vidmation_user_id": user_id}},
+ metadata={"aividio_user_id": user_id},
+ subscription_data={"metadata": {"aividio_user_id": user_id}},
)
logger.info(
"Created checkout session %s for user %s (price=%s)",
@@ -134,7 +134,7 @@ def handle_webhook(self, payload: bytes, signature: str) -> None:
settings = get_settings()
webhook_secret = settings.stripe_webhook_secret.get_secret_value()
if not webhook_secret:
- raise RuntimeError("VIDMATION_STRIPE_WEBHOOK_SECRET is not configured")
+ raise RuntimeError("AIVIDIO_STRIPE_WEBHOOK_SECRET is not configured")
try:
event = stripe.Webhook.construct_event(payload, signature, webhook_secret)
@@ -165,7 +165,7 @@ def handle_webhook(self, payload: bytes, signature: str) -> None:
def _resolve_user(self, obj: dict, db: Session) -> User | None:
"""Try to find the user from Stripe object metadata or customer ID."""
# First try our metadata
- user_id = (obj.get("metadata") or {}).get("vidmation_user_id")
+ user_id = (obj.get("metadata") or {}).get("aividio_user_id")
if user_id:
user = db.get(User, user_id)
if user:
@@ -330,7 +330,10 @@ def _on_payment_failed(self, invoice_obj: dict) -> None:
db.commit()
logger.warning("Payment failed for user %s — marked past_due", user.id[:8])
- # TODO: send dunning email via Resend
+ # Send dunning email
+ from aividio.notifications.email import send_payment_failed_email
+
+ send_payment_failed_email(to=user.email)
except Exception:
db.rollback()
logger.exception("Failed to process invoice.payment_failed")
diff --git a/src/vidmation/brand/__init__.py b/src/aividio/brand/__init__.py
similarity index 77%
rename from src/vidmation/brand/__init__.py
rename to src/aividio/brand/__init__.py
index 4fdade3..71e1b1d 100644
--- a/src/vidmation/brand/__init__.py
+++ b/src/aividio/brand/__init__.py
@@ -1,4 +1,4 @@
-"""Brand kit and template system for VIDMATION.
+"""Brand kit and template system for AIVIDIO.
Submodules:
- kit: :class:`BrandKit` dataclass for logos, colors, fonts, intros/outros.
@@ -6,14 +6,14 @@
- overlays: FFmpeg-based overlay engine for logos, watermarks, lower thirds.
"""
-from vidmation.brand.kit import BrandKit, BrandKitColors, BrandKitFonts, LowerThirdStyle
-from vidmation.brand.overlays import (
+from aividio.brand.kit import BrandKit, BrandKitColors, BrandKitFonts, LowerThirdStyle
+from aividio.brand.overlays import (
add_logo_overlay,
add_lower_third,
add_text_overlay,
add_watermark,
)
-from vidmation.brand.templates import (
+from aividio.brand.templates import (
BUILT_IN_TEMPLATES,
TemplateSectionSpec,
VideoTemplate,
diff --git a/src/vidmation/brand/kit.py b/src/aividio/brand/kit.py
similarity index 98%
rename from src/vidmation/brand/kit.py
rename to src/aividio/brand/kit.py
index fd3f404..8543940 100644
--- a/src/vidmation/brand/kit.py
+++ b/src/aividio/brand/kit.py
@@ -221,7 +221,7 @@ def apply_to_video(
output_path.parent.mkdir(parents=True, exist_ok=True)
# Lazy import to avoid circular dependency at module level
- from vidmation.brand.overlays import (
+ from aividio.brand.overlays import (
add_logo_overlay,
add_watermark,
concat_videos,
@@ -307,7 +307,7 @@ def get_caption_style(self) -> dict[str, Any]:
"""Return a caption style dict derived from the brand kit's fonts and colours.
This dict is compatible with
- :func:`vidmation.video.captions_render.generate_ass_file`.
+ :func:`aividio.video.captions_render.generate_ass_file`.
"""
return {
"font_name": self.fonts.caption,
diff --git a/src/vidmation/brand/overlays.py b/src/aividio/brand/overlays.py
similarity index 99%
rename from src/vidmation/brand/overlays.py
rename to src/aividio/brand/overlays.py
index d0f5c6b..521ddc9 100644
--- a/src/vidmation/brand/overlays.py
+++ b/src/aividio/brand/overlays.py
@@ -14,7 +14,7 @@
import ffmpeg
-from vidmation.utils.ffmpeg import FFmpegError
+from aividio.utils.ffmpeg import FFmpegError
logger = logging.getLogger(__name__)
@@ -467,7 +467,7 @@ def concat_videos(
"""Concatenate multiple video files using the ffmpeg concat demuxer.
All videos should have the same resolution and codec for seamless
- joining. This is used by :class:`~vidmation.brand.kit.BrandKit` to
+ joining. This is used by :class:`~aividio.brand.kit.BrandKit` to
prepend intro and append outro videos.
Parameters:
diff --git a/src/vidmation/brand/templates.py b/src/aividio/brand/templates.py
similarity index 99%
rename from src/vidmation/brand/templates.py
rename to src/aividio/brand/templates.py
index 01d860c..b2d77b5 100644
--- a/src/vidmation/brand/templates.py
+++ b/src/aividio/brand/templates.py
@@ -3,7 +3,7 @@
A :class:`VideoTemplate` defines the structural and aesthetic blueprint for a
video: how many sections, what visual style each section uses, transition type,
music genre, caption preset, and colour scheme. Templates are composable with
-:class:`~vidmation.brand.kit.BrandKit` for full brand consistency.
+:class:`~aividio.brand.kit.BrandKit` for full brand consistency.
Built-in templates cover the most popular faceless YouTube niches.
"""
@@ -14,7 +14,7 @@
from dataclasses import dataclass, field
from typing import Any
-from vidmation.models.video import VideoFormat
+from aividio.models.video import VideoFormat
logger = logging.getLogger(__name__)
@@ -51,7 +51,7 @@ class VideoTemplate:
"""Blueprint for a complete video's structure and aesthetics.
Templates define the creative direction without binding to specific content.
- They pair with a :class:`~vidmation.brand.kit.BrandKit` for full branding.
+ They pair with a :class:`~aividio.brand.kit.BrandKit` for full branding.
Attributes:
name: Unique identifier / slug (e.g. ``"listicle_dark"``).
diff --git a/src/vidmation/captions/__init__.py b/src/aividio/captions/__init__.py
similarity index 86%
rename from src/vidmation/captions/__init__.py
rename to src/aividio/captions/__init__.py
index a7acb9a..77217b4 100644
--- a/src/vidmation/captions/__init__.py
+++ b/src/aividio/captions/__init__.py
@@ -1,4 +1,4 @@
-"""Dynamic caption animations for VIDMATION.
+"""Dynamic caption animations for AIVIDIO.
Submagic-style animated captions with 35+ templates inspired by popular
creators and visual styles. Generates Advanced SubStation Alpha (.ass)
@@ -16,8 +16,8 @@
from __future__ import annotations
-from vidmation.captions.animator import CaptionAnimator
-from vidmation.captions.effects import (
+from aividio.captions.animator import CaptionAnimator
+from aividio.captions.effects import (
bg_highlight,
bounce_in,
color_highlight,
@@ -27,7 +27,7 @@
shake,
slide_up,
)
-from vidmation.captions.templates import (
+from aividio.captions.templates import (
TEMPLATES,
CaptionTemplate,
create_custom_template,
diff --git a/src/vidmation/captions/animator.py b/src/aividio/captions/animator.py
similarity index 98%
rename from src/vidmation/captions/animator.py
rename to src/aividio/captions/animator.py
index c5aa53a..7ee032e 100644
--- a/src/vidmation/captions/animator.py
+++ b/src/aividio/captions/animator.py
@@ -1,7 +1,7 @@
"""CaptionAnimator -- generate animated ASS subtitle files.
This is the central engine that combines word-level timestamps from Whisper
-with a :class:`~vidmation.captions.templates.CaptionTemplate` to produce a
+with a :class:`~aividio.captions.templates.CaptionTemplate` to produce a
fully animated Advanced SubStation Alpha (``.ass``) subtitle file.
The generated file can be burned into video with::
@@ -36,7 +36,7 @@
from pathlib import Path
from textwrap import dedent
-from vidmation.captions.effects import (
+from aividio.captions.effects import (
bg_highlight,
bounce_in,
color_highlight,
@@ -51,7 +51,7 @@
underline_on,
wave_offset,
)
-from vidmation.captions.templates import CaptionTemplate
+from aividio.captions.templates import CaptionTemplate
logger = logging.getLogger(__name__)
@@ -91,8 +91,8 @@ class CaptionAnimator:
Usage::
- from vidmation.captions.animator import CaptionAnimator
- from vidmation.captions.templates import get_template
+ from aividio.captions.animator import CaptionAnimator
+ from aividio.captions.templates import get_template
animator = CaptionAnimator()
template = get_template("hormozi")
@@ -290,7 +290,7 @@ def _generate_ass_header(
# Build the main Default style
header = dedent(f"""\
[Script Info]
- Title: VIDMATION Animated Captions
+ Title: AIVIDIO Animated Captions
ScriptType: v4.00+
PlayResX: {width}
PlayResY: {height}
diff --git a/src/vidmation/captions/effects.py b/src/aividio/captions/effects.py
similarity index 98%
rename from src/vidmation/captions/effects.py
rename to src/aividio/captions/effects.py
index 7565c13..e68b1e1 100644
--- a/src/vidmation/captions/effects.py
+++ b/src/aividio/captions/effects.py
@@ -2,7 +2,7 @@
Every public function returns a string of ASS override tags (wrapped in
``{...}``) that can be prepended to dialogue text. These are the atomic
-building blocks used by :class:`~vidmation.captions.animator.CaptionAnimator`.
+building blocks used by :class:`~aividio.captions.animator.CaptionAnimator`.
ASS override tag reference used here
-------------------------------------
diff --git a/src/vidmation/captions/templates.py b/src/aividio/captions/templates.py
similarity index 99%
rename from src/vidmation/captions/templates.py
rename to src/aividio/captions/templates.py
index 18ca2d5..e46c5f4 100644
--- a/src/vidmation/captions/templates.py
+++ b/src/aividio/captions/templates.py
@@ -6,7 +6,7 @@
Usage::
- from vidmation.captions.templates import get_template, list_templates
+ from aividio.captions.templates import get_template, list_templates
t = get_template("hormozi")
all_names = [t["name"] for t in list_templates()]
diff --git a/src/aividio/cli/__init__.py b/src/aividio/cli/__init__.py
new file mode 100644
index 0000000..6d6b8b7
--- /dev/null
+++ b/src/aividio/cli/__init__.py
@@ -0,0 +1 @@
+"""AIVIDIO CLI — command-line interface powered by Typer + Rich."""
diff --git a/src/vidmation/cli/agent.py b/src/aividio/cli/agent.py
similarity index 92%
rename from src/vidmation/cli/agent.py
rename to src/aividio/cli/agent.py
index 72d6338..c3a9667 100644
--- a/src/vidmation/cli/agent.py
+++ b/src/aividio/cli/agent.py
@@ -10,12 +10,12 @@
from rich.panel import Panel
if TYPE_CHECKING:
- from vidmation.agent.orchestrator import AgentOrchestrator
- from vidmation.pipeline.context import PipelineContext
+ from aividio.agent.orchestrator import AgentOrchestrator
+ from aividio.pipeline.context import PipelineContext
-from vidmation.cli.theme import console, error, header, result_panel, spinner, styled_table, warning
-from vidmation.config.profiles import ChannelProfile, get_default_profile, load_profile
-from vidmation.config.settings import get_settings
+from aividio.cli.theme import console, error, header, result_panel, spinner, styled_table, warning
+from aividio.config.profiles import ChannelProfile, get_default_profile, load_profile
+from aividio.config.settings import get_settings
agent_app = typer.Typer(
help="AI-powered video creation agent. Let Claude coordinate the entire pipeline.",
@@ -57,7 +57,7 @@ def create_video(
each step -- generating script, voiceover, captions, media, assembly,
effects, and export -- making intelligent decisions at every stage.
"""
- from vidmation.agent.orchestrator import AgentOrchestrator
+ from aividio.agent.orchestrator import AgentOrchestrator
profile = _load_profile(channel)
settings = get_settings()
@@ -127,7 +127,7 @@ def plan_video(
which services it would use, and estimated costs -- without actually
running anything.
"""
- from vidmation.agent.orchestrator import AgentOrchestrator
+ from aividio.agent.orchestrator import AgentOrchestrator
profile = _load_profile(channel)
settings = get_settings()
@@ -167,8 +167,8 @@ def review_video(
Loads the pipeline context for the given video ID and asks the agent
to evaluate quality, monetisation compliance, and viewer retention.
"""
- from vidmation.agent.orchestrator import AgentOrchestrator
- from vidmation.pipeline.context import PipelineContext
+ from aividio.agent.orchestrator import AgentOrchestrator
+ from aividio.pipeline.context import PipelineContext
settings = get_settings()
diff --git a/src/vidmation/cli/app.py b/src/aividio/cli/app.py
similarity index 67%
rename from src/vidmation/cli/app.py
rename to src/aividio/cli/app.py
index a04a1ee..1521a0b 100644
--- a/src/vidmation/cli/app.py
+++ b/src/aividio/cli/app.py
@@ -1,23 +1,23 @@
-"""Main Typer application — entry point for the ``vidmation`` CLI."""
+"""Main Typer application — entry point for the ``aividio`` CLI."""
from __future__ import annotations
import typer
-from vidmation.cli.agent import agent_app
-from vidmation.cli.audio import audio_app
-from vidmation.cli.batch import batch_app
-from vidmation.cli.channel import channel_app
-from vidmation.cli.content import content_app
-from vidmation.cli.effects import effects_app
-from vidmation.cli.flywheel import flywheel_app
-from vidmation.cli.generate import generate_app
-from vidmation.cli.job import job_app
-from vidmation.cli.server import server_app
-from vidmation.cli.youtube import youtube_app
+from aividio.cli.agent import agent_app
+from aividio.cli.audio import audio_app
+from aividio.cli.batch import batch_app
+from aividio.cli.channel import channel_app
+from aividio.cli.content import content_app
+from aividio.cli.effects import effects_app
+from aividio.cli.flywheel import flywheel_app
+from aividio.cli.generate import generate_app
+from aividio.cli.job import job_app
+from aividio.cli.server import server_app
+from aividio.cli.youtube import youtube_app
app = typer.Typer(
- name="vidmation",
+ name="aividio",
help="AI-powered faceless YouTube video automation platform.",
no_args_is_help=False,
invoke_without_command=True,
@@ -38,7 +38,7 @@
app.add_typer(server_app, name="serve", help="[bold blue]\u25b6[/] Start the web server and dashboard.")
# Register top-level worker command
-from vidmation.cli.server import worker as _worker_cmd # noqa: E402
+from aividio.cli.server import worker as _worker_cmd # noqa: E402
app.command("worker", help="Start the background job worker.")(_worker_cmd)
@@ -50,19 +50,19 @@ def _main_callback(
) -> None:
"""[bold bright_green]AIVidio[/] \u2014 AI-powered faceless video automation."""
if version:
- from vidmation.cli.theme import LOGO, TAGLINE, VERSION, console
+ from aividio.cli.theme import LOGO, TAGLINE, VERSION, console
- console.print(f"vidmation [bold bright_green]{VERSION}[/bold bright_green]")
+ console.print(f"aividio [bold bright_green]{VERSION}[/bold bright_green]")
raise typer.Exit()
if ctx.invoked_subcommand is None:
- from vidmation.cli.theme import LOGO, TAGLINE, VERSION, console
+ from aividio.cli.theme import LOGO, TAGLINE, VERSION, console
console.print(LOGO)
console.print(f" {TAGLINE} [dim]v{VERSION}[/dim]")
console.print()
- console.print(" Run [bold bright_green]vidmation --help[/bold bright_green] to see all commands.")
- console.print(" Run [bold bright_green]vidmation generate video --topic \"...\"[/bold bright_green] to create a video.")
+ console.print(" Run [bold bright_green]aividio --help[/bold bright_green] to see all commands.")
+ console.print(" Run [bold bright_green]aividio generate video --topic \"...\"[/bold bright_green] to create a video.")
console.print()
diff --git a/src/vidmation/cli/assets.py b/src/aividio/cli/assets.py
similarity index 94%
rename from src/vidmation/cli/assets.py
rename to src/aividio/cli/assets.py
index a3d2300..d5cae5f 100644
--- a/src/vidmation/cli/assets.py
+++ b/src/aividio/cli/assets.py
@@ -6,7 +6,7 @@
import typer
-from vidmation.cli.theme import (
+from aividio.cli.theme import (
console,
error,
info,
@@ -16,14 +16,14 @@
success,
warning,
)
-from vidmation.db.engine import init_db
-from vidmation.services.assets.manager import UPLOADABLE_TYPES, AssetManager
+from aividio.db.engine import init_db
+from aividio.services.assets.manager import UPLOADABLE_TYPES, AssetManager
assets_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation assets upload
+# aividio assets upload
# ---------------------------------------------------------------------------
@@ -91,7 +91,7 @@ def assets_upload(
# ---------------------------------------------------------------------------
-# vidmation assets list
+# aividio assets list
# ---------------------------------------------------------------------------
@@ -115,7 +115,7 @@ def assets_list(
mgr.close()
if not assets:
- warning("No assets found. Upload one with: [bold]vidmation assets upload --file --type transition --name \"My Transition\"[/bold]")
+ warning("No assets found. Upload one with: [bold]aividio assets upload --file --type transition --name \"My Transition\"[/bold]")
return
table = styled_table("Assets")
@@ -150,7 +150,7 @@ def assets_list(
# ---------------------------------------------------------------------------
-# vidmation assets delete
+# aividio assets delete
# ---------------------------------------------------------------------------
@@ -187,7 +187,7 @@ def assets_delete(
# ---------------------------------------------------------------------------
-# vidmation assets info
+# aividio assets info
# ---------------------------------------------------------------------------
diff --git a/src/vidmation/cli/audio.py b/src/aividio/cli/audio.py
similarity index 89%
rename from src/vidmation/cli/audio.py
rename to src/aividio/cli/audio.py
index 2a72566..2981cf7 100644
--- a/src/vidmation/cli/audio.py
+++ b/src/aividio/cli/audio.py
@@ -8,13 +8,13 @@
import typer
-from vidmation.cli.theme import console, error, result_panel, spinner, styled_table, success
+from aividio.cli.theme import console, error, result_panel, spinner, styled_table, success
audio_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation audio generate
+# aividio audio generate
# ---------------------------------------------------------------------------
@audio_app.command("generate")
@@ -36,13 +36,13 @@ def audio_generate(
visuals, and assembling the final output.
Example:
- vidmation audio generate --file podcast.mp3 --channel default
- vidmation audio generate --file lecture.wav --format portrait
+ aividio audio generate --file podcast.mp3 --channel default
+ aividio audio generate --file lecture.wav --format portrait
"""
- from vidmation.audio_first.pipeline import AudioFirstPipeline
- from vidmation.config.settings import get_settings
- from vidmation.db.engine import init_db
- from vidmation.utils.logging import setup_logging
+ from aividio.audio_first.pipeline import AudioFirstPipeline
+ from aividio.config.settings import get_settings
+ from aividio.db.engine import init_db
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -98,7 +98,7 @@ def audio_generate(
# ---------------------------------------------------------------------------
-# vidmation audio analyze
+# aividio audio analyze
# ---------------------------------------------------------------------------
@audio_app.command("analyze")
@@ -116,13 +116,13 @@ def audio_analyze(
Useful for previewing what the audio-first pipeline will produce.
Example:
- vidmation audio analyze --file podcast.mp3
- vidmation audio analyze --file lecture.wav --output analysis.json
+ aividio audio analyze --file podcast.mp3
+ aividio audio analyze --file lecture.wav --output analysis.json
"""
- from vidmation.audio_first.pipeline import AudioFirstPipeline
- from vidmation.config.settings import get_settings
- from vidmation.db.engine import init_db
- from vidmation.utils.logging import setup_logging
+ from aividio.audio_first.pipeline import AudioFirstPipeline
+ from aividio.config.settings import get_settings
+ from aividio.db.engine import init_db
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
diff --git a/src/vidmation/cli/batch.py b/src/aividio/cli/batch.py
similarity index 83%
rename from src/vidmation/cli/batch.py
rename to src/aividio/cli/batch.py
index 0fce2af..ce6f5b7 100644
--- a/src/vidmation/cli/batch.py
+++ b/src/aividio/cli/batch.py
@@ -8,13 +8,13 @@
import typer
-from vidmation.cli.theme import console, error, info, spinner, styled_table, success, warning
+from aividio.cli.theme import console, error, info, spinner, styled_table, success, warning
batch_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation batch topics
+# aividio batch topics
# ---------------------------------------------------------------------------
@batch_app.command("topics")
@@ -33,12 +33,12 @@ def batch_topics(
"""Queue multiple videos from a list of topics.
Example:
- vidmation batch topics "10 facts about space" "History of pizza" --channel science
+ aividio batch topics "10 facts about space" "History of pizza" --channel science
"""
- from vidmation.batch.generator import BatchVideoGenerator
- from vidmation.config.settings import get_settings
- from vidmation.db.engine import init_db
- from vidmation.utils.logging import setup_logging
+ from aividio.batch.generator import BatchVideoGenerator
+ from aividio.config.settings import get_settings
+ from aividio.db.engine import init_db
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -64,7 +64,7 @@ def batch_topics(
# ---------------------------------------------------------------------------
-# vidmation batch csv
+# aividio batch csv
# ---------------------------------------------------------------------------
@batch_app.command("csv")
@@ -82,12 +82,12 @@ def batch_csv(
title, format, tags, schedule_date, priority, notes.
Example:
- vidmation batch csv data/topics.csv --channel default
+ aividio batch csv data/topics.csv --channel default
"""
- from vidmation.batch.generator import BatchVideoGenerator
- from vidmation.config.settings import get_settings
- from vidmation.db.engine import init_db
- from vidmation.utils.logging import setup_logging
+ from aividio.batch.generator import BatchVideoGenerator
+ from aividio.config.settings import get_settings
+ from aividio.db.engine import init_db
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -113,7 +113,7 @@ def batch_csv(
# ---------------------------------------------------------------------------
-# vidmation batch ideas
+# aividio batch ideas
# ---------------------------------------------------------------------------
@batch_app.command("ideas")
@@ -138,13 +138,13 @@ def batch_ideas(
By default, displays the ideas. Use --enqueue to immediately queue them.
Example:
- vidmation batch ideas --channel science --count 10
- vidmation batch ideas --channel science --count 5 --enqueue
+ aividio batch ideas --channel science --count 10
+ aividio batch ideas --channel science --count 5 --enqueue
"""
- from vidmation.batch.generator import BatchVideoGenerator
- from vidmation.config.settings import get_settings
- from vidmation.db.engine import init_db
- from vidmation.utils.logging import setup_logging
+ from aividio.batch.generator import BatchVideoGenerator
+ from aividio.config.settings import get_settings
+ from aividio.db.engine import init_db
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -205,7 +205,7 @@ def batch_ideas(
# ---------------------------------------------------------------------------
-# vidmation batch rss
+# aividio batch rss
# ---------------------------------------------------------------------------
@batch_app.command("rss")
@@ -226,12 +226,12 @@ def batch_rss(
as a video topic. Great for repurposing blog content.
Example:
- vidmation batch rss "https://blog.example.com/feed" --channel default --max 5
+ aividio batch rss "https://blog.example.com/feed" --channel default --max 5
"""
- from vidmation.batch.generator import BatchVideoGenerator
- from vidmation.config.settings import get_settings
- from vidmation.db.engine import init_db
- from vidmation.utils.logging import setup_logging
+ from aividio.batch.generator import BatchVideoGenerator
+ from aividio.config.settings import get_settings
+ from aividio.db.engine import init_db
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -284,4 +284,4 @@ def _display_batch_results(
console.print(table)
success(f"{len(results)} video(s) queued successfully!")
- info("Track progress with: [bold]vidmation job list[/bold]")
+ info("Track progress with: [bold]aividio job list[/bold]")
diff --git a/src/vidmation/cli/channel.py b/src/aividio/cli/channel.py
similarity index 92%
rename from src/vidmation/cli/channel.py
rename to src/aividio/cli/channel.py
index dbd6ce2..3a8e8ca 100644
--- a/src/vidmation/cli/channel.py
+++ b/src/aividio/cli/channel.py
@@ -6,7 +6,7 @@
import typer
-from vidmation.cli.theme import (
+from aividio.cli.theme import (
console,
error,
status_badge,
@@ -14,14 +14,14 @@
success,
warning,
)
-from vidmation.db.engine import get_session, init_db
-from vidmation.db.repos import ChannelRepo
+from aividio.db.engine import get_session, init_db
+from aividio.db.repos import ChannelRepo
channel_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation channel list
+# aividio channel list
# ---------------------------------------------------------------------------
@channel_app.command("list")
@@ -37,7 +37,7 @@ def channel_list(
session.close()
if not channels:
- warning("No channels found. Create one with: [bold]vidmation channel add[/bold]")
+ warning("No channels found. Create one with: [bold]aividio channel add[/bold]")
return
table = styled_table("Channels")
@@ -62,7 +62,7 @@ def channel_list(
# ---------------------------------------------------------------------------
-# vidmation channel add
+# aividio channel add
# ---------------------------------------------------------------------------
@channel_app.command("add")
@@ -104,7 +104,7 @@ def channel_add(
# ---------------------------------------------------------------------------
-# vidmation channel auth
+# aividio channel auth
# ---------------------------------------------------------------------------
@channel_app.command("auth")
@@ -130,7 +130,7 @@ def channel_auth(
console.print(f"Starting OAuth flow for channel [cyan]{name}[/cyan]...")
try:
- from vidmation.services.youtube.auth import get_credentials
+ from aividio.services.youtube.auth import get_credentials
creds = get_credentials(channel_name=name)
@@ -153,7 +153,7 @@ def channel_auth(
# ---------------------------------------------------------------------------
-# vidmation channel remove (bonus utility)
+# aividio channel remove (bonus utility)
# ---------------------------------------------------------------------------
@channel_app.command("deactivate")
diff --git a/src/vidmation/cli/content.py b/src/aividio/cli/content.py
similarity index 93%
rename from src/vidmation/cli/content.py
rename to src/aividio/cli/content.py
index 2df0bbe..33183b1 100644
--- a/src/vidmation/cli/content.py
+++ b/src/aividio/cli/content.py
@@ -9,15 +9,15 @@
import typer
from rich.panel import Panel
-from vidmation.cli.theme import console, info, spinner, styled_table, success
-from vidmation.config.settings import get_settings
-from vidmation.db.engine import init_db
+from aividio.cli.theme import console, info, spinner, styled_table, success
+from aividio.config.settings import get_settings
+from aividio.db.engine import init_db
content_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation content plan
+# aividio content plan
# ---------------------------------------------------------------------------
@content_app.command("plan")
@@ -33,8 +33,8 @@ def content_plan(
Creates a multi-week content plan with topics, formats, keywords,
and priorities. Saves to the data directory by default.
"""
- from vidmation.content.calendar import ContentCalendar
- from vidmation.content.planner import ContentPlanner
+ from aividio.content.calendar import ContentCalendar
+ from aividio.content.planner import ContentPlanner
init_db()
settings = get_settings()
@@ -95,7 +95,7 @@ def content_plan(
# ---------------------------------------------------------------------------
-# vidmation content trending
+# aividio content trending
# ---------------------------------------------------------------------------
@content_app.command("trending")
@@ -108,7 +108,7 @@ def content_trending(
Uses AI to identify timely, high-potential video topics.
"""
- from vidmation.content.planner import ContentPlanner
+ from aividio.content.planner import ContentPlanner
settings = get_settings()
@@ -155,7 +155,7 @@ def content_trending(
# ---------------------------------------------------------------------------
-# vidmation content series
+# aividio content series
# ---------------------------------------------------------------------------
@content_app.command("series")
@@ -168,13 +168,13 @@ def content_series(
Shows all existing series with episode counts and progress.
Use --suggest to get AI-generated series ideas.
"""
- from vidmation.content.series import SeriesManager
+ from aividio.content.series import SeriesManager
init_db()
manager = SeriesManager()
if suggest:
- from vidmation.content.planner import ContentPlanner
+ from aividio.content.planner import ContentPlanner
settings = get_settings()
@@ -201,7 +201,7 @@ def content_series(
if not series_list:
console.print("[dim]No series found.[/dim]")
- console.print(" Create one: [bold]vidmation content series --suggest --channel default[/bold]")
+ console.print(" Create one: [bold]aividio content series --suggest --channel default[/bold]")
return
table = styled_table("Video Series")
@@ -232,7 +232,7 @@ def content_series(
# ---------------------------------------------------------------------------
-# vidmation content gaps
+# aividio content gaps
# ---------------------------------------------------------------------------
@content_app.command("gaps")
@@ -245,7 +245,7 @@ def content_gaps(
Examines existing videos and identifies untapped topics and
growth opportunities.
"""
- from vidmation.content.planner import ContentPlanner
+ from aividio.content.planner import ContentPlanner
init_db()
settings = get_settings()
@@ -293,7 +293,7 @@ def content_gaps(
# ---------------------------------------------------------------------------
-# vidmation content keywords
+# aividio content keywords
# ---------------------------------------------------------------------------
@content_app.command("keywords")
@@ -306,7 +306,7 @@ def content_keywords(
Analyses keyword opportunity, competition, and suggests angles.
"""
- from vidmation.seo.optimizer import SEOOptimizer
+ from aividio.seo.optimizer import SEOOptimizer
settings = get_settings()
diff --git a/src/vidmation/cli/effects.py b/src/aividio/cli/effects.py
similarity index 90%
rename from src/vidmation/cli/effects.py
rename to src/aividio/cli/effects.py
index 2b16ae3..b1c80de 100644
--- a/src/vidmation/cli/effects.py
+++ b/src/aividio/cli/effects.py
@@ -12,7 +12,7 @@
import typer
-from vidmation.cli.theme import (
+from aividio.cli.theme import (
console,
error,
info,
@@ -51,9 +51,9 @@ def _load_timestamps(timestamps_file: str | None, video_path: Path) -> list[dict
return data.get("words", data.get("word_timestamps", []))
# Auto-transcribe.
- from vidmation.config.settings import get_settings
- from vidmation.services.captions.whisper import WhisperCaptionGenerator
- from vidmation.utils.ffmpeg import run_ffmpeg
+ from aividio.config.settings import get_settings
+ from aividio.services.captions.whisper import WhisperCaptionGenerator
+ from aividio.utils.ffmpeg import run_ffmpeg
info("No timestamps file provided; auto-transcribing...")
@@ -102,7 +102,7 @@ def _validate_input(video_path: str) -> Path:
# ---------------------------------------------------------------------------
-# vidmation effects zoom
+# aividio effects zoom
# ---------------------------------------------------------------------------
@effects_app.command("zoom")
@@ -125,11 +125,11 @@ def effects_zoom(
applies smooth zoom-in/out effects.
Examples:
- vidmation effects zoom --input video.mp4 --style smooth
- vidmation effects zoom -i video.mp4 -s crash -n 5 -o output.mp4
+ aividio effects zoom --input video.mp4 --style smooth
+ aividio effects zoom -i video.mp4 -s crash -n 5 -o output.mp4
"""
- from vidmation.effects.magic_zoom import MagicZoom
- from vidmation.utils.logging import setup_logging
+ from aividio.effects.magic_zoom import MagicZoom
+ from aividio.utils.logging import setup_logging
setup_logging()
@@ -185,7 +185,7 @@ def effects_zoom(
# ---------------------------------------------------------------------------
-# vidmation effects silence
+# aividio effects silence
# ---------------------------------------------------------------------------
@effects_app.command("silence")
@@ -208,11 +208,11 @@ def effects_silence(
"um", "uh", "like", "you know", etc.
Examples:
- vidmation effects silence --input video.mp4 --mode fast
- vidmation effects silence -i video.mp4 -m extra_fast --no-fillers
+ aividio effects silence --input video.mp4 --mode fast
+ aividio effects silence -i video.mp4 -m extra_fast --no-fillers
"""
- from vidmation.effects.silence_remover import SilenceRemover
- from vidmation.utils.logging import setup_logging
+ from aividio.effects.silence_remover import SilenceRemover
+ from aividio.utils.logging import setup_logging
setup_logging()
@@ -265,7 +265,7 @@ def effects_silence(
# ---------------------------------------------------------------------------
-# vidmation effects broll
+# aividio effects broll
# ---------------------------------------------------------------------------
@effects_app.command("broll")
@@ -288,11 +288,11 @@ def effects_broll(
searches stock media for matching clips, and inserts them.
Examples:
- vidmation effects broll --input video.mp4 --max-clips 8
- vidmation effects broll -i video.mp4 -b picture_in_picture -n 5
+ aividio effects broll --input video.mp4 --max-clips 8
+ aividio effects broll -i video.mp4 -b picture_in_picture -n 5
"""
- from vidmation.effects.magic_broll import MagicBRoll
- from vidmation.utils.logging import setup_logging
+ from aividio.effects.magic_broll import MagicBRoll
+ from aividio.utils.logging import setup_logging
setup_logging()
@@ -347,7 +347,7 @@ def effects_broll(
# ---------------------------------------------------------------------------
-# vidmation effects emoji
+# aividio effects emoji
# ---------------------------------------------------------------------------
@effects_app.command("emoji")
@@ -367,11 +367,11 @@ def effects_emoji(
Optionally adds sound effects at transition and emphasis points.
Examples:
- vidmation effects emoji --input video.mp4
- vidmation effects emoji -i video.mp4 --no-sfx
+ aividio effects emoji --input video.mp4
+ aividio effects emoji -i video.mp4 --no-sfx
"""
- from vidmation.effects.emoji_sfx import EmojiSFXEngine
- from vidmation.utils.logging import setup_logging
+ from aividio.effects.emoji_sfx import EmojiSFXEngine
+ from aividio.utils.logging import setup_logging
setup_logging()
@@ -404,7 +404,7 @@ def effects_emoji(
# ---------------------------------------------------------------------------
-# vidmation effects clips
+# aividio effects clips
# ---------------------------------------------------------------------------
@effects_app.command("clips")
@@ -432,11 +432,11 @@ def effects_clips(
reformats to the target aspect ratio, and optionally burns in captions.
Examples:
- vidmation effects clips --input video.mp4 --count 5 --format portrait
- vidmation effects clips -i podcast.mp4 -n 3 -f square --no-captions
+ aividio effects clips --input video.mp4 --count 5 --format portrait
+ aividio effects clips -i podcast.mp4 -n 3 -f square --no-captions
"""
- from vidmation.effects.magic_clips import MagicClips
- from vidmation.utils.logging import setup_logging
+ from aividio.effects.magic_clips import MagicClips
+ from aividio.utils.logging import setup_logging
setup_logging()
@@ -505,7 +505,7 @@ def effects_clips(
# ---------------------------------------------------------------------------
-# vidmation effects all
+# aividio effects all
# ---------------------------------------------------------------------------
@effects_app.command("all")
@@ -528,15 +528,15 @@ def effects_all(
3. Emoji + SFX enhancement
Examples:
- vidmation effects all --input video.mp4
- vidmation effects all -i video.mp4 --silence-mode fast --zoom-style crash
+ aividio effects all --input video.mp4
+ aividio effects all -i video.mp4 --silence-mode fast --zoom-style crash
"""
import tempfile
- from vidmation.effects.emoji_sfx import EmojiSFXEngine
- from vidmation.effects.magic_zoom import MagicZoom
- from vidmation.effects.silence_remover import SilenceRemover
- from vidmation.utils.logging import setup_logging
+ from aividio.effects.emoji_sfx import EmojiSFXEngine
+ from aividio.effects.magic_zoom import MagicZoom
+ from aividio.effects.silence_remover import SilenceRemover
+ from aividio.utils.logging import setup_logging
setup_logging()
@@ -559,7 +559,7 @@ def effects_all(
],
))
- with tempfile.TemporaryDirectory(prefix="vidmation_effects_") as tmp_dir:
+ with tempfile.TemporaryDirectory(prefix="aividio_effects_") as tmp_dir:
tmp_path = Path(tmp_dir)
# Step 1: Silence removal.
diff --git a/src/vidmation/cli/flywheel.py b/src/aividio/cli/flywheel.py
similarity index 96%
rename from src/vidmation/cli/flywheel.py
rename to src/aividio/cli/flywheel.py
index b960a3b..6af98bc 100644
--- a/src/vidmation/cli/flywheel.py
+++ b/src/aividio/cli/flywheel.py
@@ -8,7 +8,7 @@
import typer
-from vidmation.cli.theme import (
+from aividio.cli.theme import (
console,
error,
info,
@@ -18,7 +18,7 @@
success,
warning,
)
-from vidmation.config.settings import get_settings
+from aividio.config.settings import get_settings
flywheel_app = typer.Typer(no_args_is_help=True)
@@ -58,7 +58,7 @@
# ---------------------------------------------------------------------------
-# vidmation flywheel run
+# aividio flywheel run
# ---------------------------------------------------------------------------
@flywheel_app.command("run")
@@ -135,8 +135,8 @@ def flywheel_run(
if not skip_copy:
info("Step 1: Generating social media copy with AI...")
try:
- from vidmation.config.profiles import get_default_profile
- from vidmation.services.repurpose import create_repurposer
+ from aividio.config.profiles import get_default_profile
+ from aividio.services.repurpose import create_repurposer
with spinner("AI generating platform-specific content..."):
repurposer = create_repurposer(settings=settings)
@@ -167,7 +167,7 @@ def flywheel_run(
if not skip_video:
info("Step 2: Reformatting video for each platform...")
try:
- from vidmation.platforms.exporter import MultiPlatformExporter
+ from aividio.platforms.exporter import MultiPlatformExporter
exporter = MultiPlatformExporter(output_dir=out_dir)
@@ -257,7 +257,7 @@ def flywheel_run(
# ---------------------------------------------------------------------------
-# vidmation flywheel copy
+# aividio flywheel copy
# ---------------------------------------------------------------------------
@flywheel_app.command("copy")
@@ -288,8 +288,8 @@ def flywheel_copy(
else:
copy_targets = list(COPY_PLATFORMS)
- from vidmation.config.profiles import get_default_profile
- from vidmation.services.repurpose import create_repurposer
+ from aividio.config.profiles import get_default_profile
+ from aividio.services.repurpose import create_repurposer
with spinner("Generating social media copy..."):
repurposer = create_repurposer(settings=settings)
@@ -309,13 +309,13 @@ def flywheel_copy(
# ---------------------------------------------------------------------------
-# vidmation flywheel platforms
+# aividio flywheel platforms
# ---------------------------------------------------------------------------
@flywheel_app.command("platforms")
def flywheel_platforms() -> None:
"""List all supported flywheel platforms and their specs."""
- from vidmation.platforms.exporter import MultiPlatformExporter
+ from aividio.platforms.exporter import MultiPlatformExporter
exporter = MultiPlatformExporter()
platforms = exporter.get_supported_platforms()
diff --git a/src/vidmation/cli/generate.py b/src/aividio/cli/generate.py
similarity index 86%
rename from src/vidmation/cli/generate.py
rename to src/aividio/cli/generate.py
index 9016a20..43306fb 100644
--- a/src/vidmation/cli/generate.py
+++ b/src/aividio/cli/generate.py
@@ -8,7 +8,7 @@
import typer
-from vidmation.cli.theme import (
+from aividio.cli.theme import (
console,
error,
header,
@@ -20,16 +20,16 @@
success,
warning,
)
-from vidmation.config.profiles import ChannelProfile, get_default_profile, load_profile
-from vidmation.config.settings import get_settings
-from vidmation.db.engine import init_db
-from vidmation.models.video import VideoFormat
+from aividio.config.profiles import ChannelProfile, get_default_profile, load_profile
+from aividio.config.settings import get_settings
+from aividio.db.engine import init_db
+from aividio.models.video import VideoFormat
generate_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation generate styles (list available video style templates)
+# aividio generate styles (list available video style templates)
# ---------------------------------------------------------------------------
@generate_app.command("styles")
@@ -38,10 +38,10 @@ def list_style_templates() -> None:
Each template defines a complete visual identity: caption style,
transitions, music genre, thumbnail style, accent colour, and title
- placement. Use ``--style `` with ``vidmation generate video``
+ placement. Use ``--style `` with ``aividio generate video``
to apply a template.
"""
- from vidmation.styles.registry import list_templates
+ from aividio.styles.registry import list_templates
templates = list_templates()
@@ -75,21 +75,21 @@ def list_style_templates() -> None:
console.print(table)
console.print()
console.print(
- " [dim]Tip:[/dim] Use [bold bright_green]vidmation generate video "
+ " [dim]Tip:[/dim] Use [bold bright_green]aividio generate video "
"--topic \"...\" --style [/bold bright_green] to apply a template."
)
console.print()
# ---------------------------------------------------------------------------
-# vidmation generate video
+# aividio generate video
# ---------------------------------------------------------------------------
@generate_app.command("video")
def generate_video(
topic: str = typer.Option(..., "--topic", "-t", help="Video topic / prompt."),
channel: str = typer.Option("default", "--channel", "-c", help="Channel name."),
- style: Optional[str] = typer.Option(None, "--style", "-s", help="Video style template slug (e.g. dark-cinematic). Run 'vidmation generate styles' to list."),
+ style: Optional[str] = typer.Option(None, "--style", "-s", help="Video style template slug (e.g. dark-cinematic). Run 'aividio generate styles' to list."),
format: str = typer.Option("landscape", "--format", "-f", help="Video format: landscape, portrait, short."),
no_upload: bool = typer.Option(False, "--no-upload", help="Skip YouTube upload stage."),
run_async: bool = typer.Option(False, "--async", help="Queue the job instead of running synchronously."),
@@ -101,9 +101,9 @@ def generate_video(
Use --style / -s to apply a built-in video style template that controls
captions, transitions, music genre, thumbnail style, and colour accent.
- Run ``vidmation generate styles`` to see all available templates.
+ Run ``aividio generate styles`` to see all available templates.
"""
- from vidmation.utils.logging import setup_logging
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -124,10 +124,10 @@ def _generate_video_async(
style_slug: str | None = None,
) -> None:
"""Enqueue a video generation job."""
- from vidmation.queue.tasks import enqueue_video
+ from aividio.queue.tasks import enqueue_video
if style_slug:
- from vidmation.styles.registry import get_template
+ from aividio.styles.registry import get_template
try:
get_template(style_slug) # validate slug exists
except ValueError as exc:
@@ -147,7 +147,7 @@ def _generate_video_async(
rows = [
("Video ID:", f"[id]{video.id}[/id]"),
("Job ID:", f"[id]{job.id}[/id]"),
- ("Track:", f"[bold]vidmation job status {job.id}[/bold]"),
+ ("Track:", f"[bold]aividio job status {job.id}[/bold]"),
]
if style_slug:
rows.insert(0, ("Style:", f"[accent]{style_slug}[/accent]"))
@@ -166,12 +166,12 @@ def _generate_video_sync(
"""Run the full pipeline synchronously with Rich progress output."""
import uuid
- from vidmation.db.engine import get_session
- from vidmation.db.repos import ChannelRepo
- from vidmation.pipeline.context import PipelineContext
- from vidmation.pipeline.orchestrator import PipelineOrchestrator
- from vidmation.pipeline.stages import STAGE_REGISTRY
- from vidmation.utils.files import get_work_dir
+ from aividio.db.engine import get_session
+ from aividio.db.repos import ChannelRepo
+ from aividio.pipeline.context import PipelineContext
+ from aividio.pipeline.orchestrator import PipelineOrchestrator
+ from aividio.pipeline.stages import STAGE_REGISTRY
+ from aividio.utils.files import get_work_dir
settings = get_settings()
session = get_session()
@@ -182,7 +182,7 @@ def _generate_video_sync(
if ch is None:
error(
f"Channel '{channel}' not found. "
- f"Create it with: [bold]vidmation channel add --name '{channel}'[/bold]"
+ f"Create it with: [bold]aividio channel add --name '{channel}'[/bold]"
)
session.close()
raise typer.Exit(1)
@@ -195,7 +195,7 @@ def _generate_video_sync(
# Apply video style template if provided
if style_slug:
- from vidmation.styles.registry import apply_template
+ from aividio.styles.registry import apply_template
try:
profile = apply_template(profile, style_slug)
@@ -276,7 +276,7 @@ def tracked_run(context, **kwargs):
# ---------------------------------------------------------------------------
-# vidmation generate script
+# aividio generate script
# ---------------------------------------------------------------------------
@generate_app.command("script")
@@ -286,7 +286,7 @@ def generate_script(
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path (default: stdout)."),
) -> None:
"""Generate a script only and output JSON."""
- from vidmation.utils.logging import setup_logging
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -294,7 +294,7 @@ def generate_script(
settings = get_settings()
profile = _resolve_profile(channel)
- from vidmation.services.scriptgen import create_script_generator
+ from aividio.services.scriptgen import create_script_generator
with spinner("Generating script..."):
generator = create_script_generator(settings=settings)
@@ -310,7 +310,7 @@ def generate_script(
# ---------------------------------------------------------------------------
-# vidmation generate voiceover
+# aividio generate voiceover
# ---------------------------------------------------------------------------
@generate_app.command("voiceover")
@@ -320,7 +320,7 @@ def generate_voiceover(
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output audio file path."),
) -> None:
"""Generate a voiceover from an existing script file."""
- from vidmation.utils.logging import setup_logging
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -334,7 +334,7 @@ def generate_voiceover(
settings = get_settings()
profile = _resolve_profile(channel)
- from vidmation.services.tts import create_tts_provider
+ from aividio.services.tts import create_tts_provider
# Build narration text
parts: list[str] = []
@@ -360,7 +360,7 @@ def generate_voiceover(
# ---------------------------------------------------------------------------
-# vidmation generate thumbnail
+# aividio generate thumbnail
# ---------------------------------------------------------------------------
@generate_app.command("thumbnail")
@@ -369,13 +369,13 @@ def generate_thumbnail(
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output image path."),
) -> None:
"""Generate a thumbnail for an existing video."""
- from vidmation.utils.logging import setup_logging
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
- from vidmation.db.engine import get_session
- from vidmation.db.repos import ChannelRepo, VideoRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import ChannelRepo, VideoRepo
settings = get_settings()
session = get_session()
@@ -394,7 +394,7 @@ def generate_thumbnail(
script = video.script_json or {}
title = script.get("title", video.topic_prompt)
- from vidmation.services.imagegen import create_image_generator
+ from aividio.services.imagegen import create_image_generator
output_path = Path(output) if output else Path(f"thumbnail_{video_id[:8]}.png")
@@ -413,7 +413,7 @@ def generate_thumbnail(
# ---------------------------------------------------------------------------
-# vidmation generate blog
+# aividio generate blog
# ---------------------------------------------------------------------------
@generate_app.command("blog")
@@ -430,10 +430,10 @@ def generate_from_blog(
a structured video script optimised for YouTube.
Example:
- vidmation generate blog --url https://example.com/my-post
- vidmation generate blog --url https://example.com/my-post --video
+ aividio generate blog --url https://example.com/my-post
+ aividio generate blog --url https://example.com/my-post --video
"""
- from vidmation.utils.logging import setup_logging
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -442,7 +442,7 @@ def generate_from_blog(
profile = _resolve_profile(channel)
# Step 1: Scrape and convert blog to script
- from vidmation.services.blog2video import create_blog_converter
+ from aividio.services.blog2video import create_blog_converter
with spinner("Scraping blog and generating script..."):
converter = create_blog_converter(settings=settings)
@@ -475,11 +475,11 @@ def generate_from_blog(
import uuid
- from vidmation.models.video import VideoFormat
- from vidmation.pipeline.context import PipelineContext
- from vidmation.pipeline.orchestrator import PipelineOrchestrator
- from vidmation.pipeline.stages import STAGE_REGISTRY
- from vidmation.utils.files import get_work_dir
+ from aividio.models.video import VideoFormat
+ from aividio.pipeline.context import PipelineContext
+ from aividio.pipeline.orchestrator import PipelineOrchestrator
+ from aividio.pipeline.stages import STAGE_REGISTRY
+ from aividio.utils.files import get_work_dir
video_id = str(uuid.uuid4())
work_dir = get_work_dir(video_id)
@@ -541,8 +541,8 @@ def generate_from_blog(
def _resolve_profile(channel_name: str) -> ChannelProfile:
"""Load the channel profile by channel name, falling back to defaults."""
- from vidmation.db.engine import get_session
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import ChannelRepo
session = get_session()
try:
diff --git a/src/vidmation/cli/job.py b/src/aividio/cli/job.py
similarity index 93%
rename from src/vidmation/cli/job.py
rename to src/aividio/cli/job.py
index e2dc552..1f52181 100644
--- a/src/vidmation/cli/job.py
+++ b/src/aividio/cli/job.py
@@ -6,7 +6,7 @@
import typer
-from vidmation.cli.theme import (
+from aividio.cli.theme import (
console,
error,
result_panel,
@@ -15,15 +15,15 @@
success,
warning,
)
-from vidmation.db.engine import get_session, init_db
-from vidmation.db.repos import JobRepo, VideoRepo
-from vidmation.models.job import JobStatus
+from aividio.db.engine import get_session, init_db
+from aividio.db.repos import JobRepo, VideoRepo
+from aividio.models.job import JobStatus
job_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation job list
+# aividio job list
# ---------------------------------------------------------------------------
@job_app.command("list")
@@ -69,7 +69,7 @@ def job_list(
# ---------------------------------------------------------------------------
-# vidmation job status
+# aividio job status
# ---------------------------------------------------------------------------
@job_app.command("status")
@@ -122,7 +122,7 @@ def job_status(
# ---------------------------------------------------------------------------
-# vidmation job cancel
+# aividio job cancel
# ---------------------------------------------------------------------------
@job_app.command("cancel")
@@ -158,7 +158,7 @@ def job_cancel(
# ---------------------------------------------------------------------------
-# vidmation job retry
+# aividio job retry
# ---------------------------------------------------------------------------
@job_app.command("retry")
@@ -175,7 +175,7 @@ def job_retry(
"""
init_db()
- from vidmation.queue.tasks import enqueue_retry
+ from aividio.queue.tasks import enqueue_retry
try:
new_job = enqueue_retry(job_id=job_id, resume_from=stage)
@@ -188,6 +188,6 @@ def job_retry(
[
("New Job ID:", f"[cyan]{new_job.id}[/cyan]"),
("Resume From:", new_job.resume_from_stage or "beginning"),
- ("Track with:", f"[bold]vidmation job status {new_job.id}[/bold]"),
+ ("Track with:", f"[bold]aividio job status {new_job.id}[/bold]"),
],
))
diff --git a/src/vidmation/cli/server.py b/src/aividio/cli/server.py
similarity index 87%
rename from src/vidmation/cli/server.py
rename to src/aividio/cli/server.py
index 8f049af..b077be4 100644
--- a/src/vidmation/cli/server.py
+++ b/src/aividio/cli/server.py
@@ -6,7 +6,7 @@
import typer
-from vidmation.cli.theme import (
+from aividio.cli.theme import (
LOGO,
TAGLINE,
VERSION,
@@ -14,13 +14,13 @@
header,
success,
)
-from vidmation.db.engine import init_db
+from aividio.db.engine import init_db
server_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation serve
+# aividio serve
# ---------------------------------------------------------------------------
@server_app.command("start")
@@ -32,8 +32,8 @@ def serve(
"""Start the FastAPI web server."""
import uvicorn
- from vidmation.config.settings import get_settings
- from vidmation.utils.logging import setup_logging
+ from aividio.config.settings import get_settings
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -49,7 +49,7 @@ def serve(
success(f"Starting web server at [url]http://{bind_host}:{bind_port}[/url]")
uvicorn.run(
- "vidmation.web.app:app",
+ "aividio.web.app:app",
host=bind_host,
port=bind_port,
reload=reload,
@@ -57,7 +57,7 @@ def serve(
# ---------------------------------------------------------------------------
-# vidmation worker
+# aividio worker
# ---------------------------------------------------------------------------
def worker(
@@ -72,9 +72,9 @@ def worker(
Use --with-web to also start the FastAPI web server in a thread.
Use --with-scheduler to enable automatic scheduled video generation.
"""
- from vidmation.config.settings import get_settings
- from vidmation.queue.worker import JobWorker
- from vidmation.utils.logging import setup_logging
+ from aividio.config.settings import get_settings
+ from aividio.queue.worker import JobWorker
+ from aividio.utils.logging import setup_logging
setup_logging()
init_db()
@@ -110,7 +110,7 @@ def _start_web_thread(host: str, port: int) -> threading.Thread:
def _run():
uvicorn.run(
- "vidmation.web.app:app",
+ "aividio.web.app:app",
host=host,
port=port,
log_level="warning",
@@ -124,7 +124,7 @@ def _run():
def _start_scheduler_thread(settings) -> threading.Thread:
"""Run the video scheduler in a daemon thread."""
- from vidmation.queue.scheduler import VideoScheduler
+ from aividio.queue.scheduler import VideoScheduler
scheduler = VideoScheduler(settings=settings)
t = scheduler.run_in_thread()
diff --git a/src/vidmation/cli/theme.py b/src/aividio/cli/theme.py
similarity index 98%
rename from src/vidmation/cli/theme.py
rename to src/aividio/cli/theme.py
index 68d4d29..f896697 100644
--- a/src/vidmation/cli/theme.py
+++ b/src/aividio/cli/theme.py
@@ -1,11 +1,11 @@
-"""VIDMATION CLI design system — shared branding, colors, and styled components.
+"""AIVIDIO CLI design system — shared branding, colors, and styled components.
Provides a consistent, premium visual language across all CLI commands.
Inspired by modern CLIs (Vercel, Stripe, Railway).
Usage::
- from vidmation.cli.theme import (
+ from aividio.cli.theme import (
console, err, brand, success, error, warning, info,
header, divider, kv, styled_table, LOGO,
)
diff --git a/src/vidmation/cli/youtube.py b/src/aividio/cli/youtube.py
similarity index 91%
rename from src/vidmation/cli/youtube.py
rename to src/aividio/cli/youtube.py
index 3a1529b..f0f0d6d 100644
--- a/src/vidmation/cli/youtube.py
+++ b/src/aividio/cli/youtube.py
@@ -10,9 +10,9 @@
import typer
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
-from vidmation.cli.theme import (
+from aividio.cli.theme import (
console,
error,
header,
@@ -25,14 +25,14 @@
success,
warning,
)
-from vidmation.config.settings import get_settings
-from vidmation.db.engine import get_session, init_db
+from aividio.config.settings import get_settings
+from aividio.db.engine import get_session, init_db
youtube_app = typer.Typer(no_args_is_help=True)
# ---------------------------------------------------------------------------
-# vidmation youtube setup
+# aividio youtube setup
# ---------------------------------------------------------------------------
@youtube_app.command("setup")
@@ -62,7 +62,7 @@ def youtube_setup(
console.print(header(
"YouTube API Setup",
- subtitle="Connect VIDMATION to your YouTube channel.\n"
+ subtitle="Connect AIVIDIO to your YouTube channel.\n"
"You need a Google Cloud project with the YouTube Data API v3 enabled.",
))
@@ -108,7 +108,7 @@ def youtube_setup(
step(2, "Authenticating with YouTube...")
info("A browser window will open for Google sign-in.")
- from vidmation.services.youtube.auth import get_credentials
+ from aividio.services.youtube.auth import get_credentials
try:
creds = get_credentials(
@@ -123,7 +123,7 @@ def youtube_setup(
step(3, "Verifying access...")
try:
- from vidmation.services.youtube.auth import fetch_youtube_channel_info
+ from aividio.services.youtube.auth import fetch_youtube_channel_info
ch_info = fetch_youtube_channel_info(creds)
@@ -151,8 +151,8 @@ def _youtube_setup_for_channel(
settings: "Settings",
) -> None:
"""Run per-channel OAuth setup and store token in the DB."""
- from vidmation.db.repos import ChannelRepo
- from vidmation.services.youtube.auth import (
+ from aividio.db.repos import ChannelRepo
+ from aividio.services.youtube.auth import (
fetch_youtube_channel_info,
get_credentials_for_channel,
)
@@ -167,7 +167,7 @@ def _youtube_setup_for_channel(
if channel_record is None:
error(
f"Channel '{channel_name}' not found in the database.\n"
- f"Create it first with: [bold]vidmation channel add --name {channel_name}[/bold]"
+ f"Create it first with: [bold]aividio channel add --name {channel_name}[/bold]"
)
raise typer.Exit(1)
@@ -226,7 +226,7 @@ def _youtube_setup_for_channel(
# ---------------------------------------------------------------------------
-# vidmation youtube upload
+# aividio youtube upload
# ---------------------------------------------------------------------------
@youtube_app.command("upload")
@@ -258,11 +258,11 @@ def youtube_upload(
if not token_path.exists():
error(
"YouTube not configured. "
- "Run [bold]vidmation youtube setup[/bold] first."
+ "Run [bold]aividio youtube setup[/bold] first."
)
raise typer.Exit(1)
- from vidmation.services.youtube.auth import get_credentials
+ from aividio.services.youtube.auth import get_credentials
creds = get_credentials(token_path=token_path, client_secret_path=client_secret_path)
@@ -273,11 +273,11 @@ def youtube_upload(
script = json.loads(Path(script_file).read_text(encoding="utf-8"))
if script:
- from vidmation.services.youtube.metadata import YouTubeMetadataGenerator
+ from aividio.services.youtube.metadata import YouTubeMetadataGenerator
with spinner("Generating AI metadata..."):
meta_gen = YouTubeMetadataGenerator(settings=settings)
- from vidmation.config.profiles import get_default_profile
+ from aividio.config.profiles import get_default_profile
metadata = meta_gen.generate(script=script, channel_profile=get_default_profile())
@@ -302,7 +302,7 @@ def youtube_upload(
visibility = "private" # Must be private for scheduled publishing
info(f"Scheduled for: {publish_at.isoformat()}")
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.uploader import YouTubeUploader
uploader = YouTubeUploader(credentials=creds)
@@ -355,7 +355,7 @@ def youtube_upload(
# ---------------------------------------------------------------------------
-# vidmation youtube list
+# aividio youtube list
# ---------------------------------------------------------------------------
@youtube_app.command("list")
@@ -368,11 +368,11 @@ def youtube_list(
client_secret_path = settings.data_dir / "client_secret.json"
if not token_path.exists():
- error("Run [bold]vidmation youtube setup[/bold] first.")
+ error("Run [bold]aividio youtube setup[/bold] first.")
raise typer.Exit(1)
- from vidmation.services.youtube.auth import get_credentials
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.auth import get_credentials
+ from aividio.services.youtube.uploader import YouTubeUploader
creds = get_credentials(token_path=token_path, client_secret_path=client_secret_path)
uploader = YouTubeUploader(credentials=creds)
@@ -404,7 +404,7 @@ def youtube_list(
# ---------------------------------------------------------------------------
-# vidmation youtube status
+# aividio youtube status
# ---------------------------------------------------------------------------
@youtube_app.command("status")
@@ -417,11 +417,11 @@ def youtube_status(
client_secret_path = settings.data_dir / "client_secret.json"
if not token_path.exists():
- error("Run [bold]vidmation youtube setup[/bold] first.")
+ error("Run [bold]aividio youtube setup[/bold] first.")
raise typer.Exit(1)
- from vidmation.services.youtube.auth import get_credentials
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.auth import get_credentials
+ from aividio.services.youtube.uploader import YouTubeUploader
creds = get_credentials(token_path=token_path, client_secret_path=client_secret_path)
uploader = YouTubeUploader(credentials=creds)
@@ -454,7 +454,7 @@ def youtube_status(
# ---------------------------------------------------------------------------
-# vidmation youtube update
+# aividio youtube update
# ---------------------------------------------------------------------------
@youtube_app.command("update")
@@ -473,8 +473,8 @@ def youtube_update(
token_path = settings.data_dir / "youtube_token.json"
client_secret_path = settings.data_dir / "client_secret.json"
- from vidmation.services.youtube.auth import get_credentials
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.auth import get_credentials
+ from aividio.services.youtube.uploader import YouTubeUploader
creds = get_credentials(token_path=token_path, client_secret_path=client_secret_path)
uploader = YouTubeUploader(credentials=creds)
diff --git a/src/aividio/config/__init__.py b/src/aividio/config/__init__.py
new file mode 100644
index 0000000..6647c68
--- /dev/null
+++ b/src/aividio/config/__init__.py
@@ -0,0 +1,6 @@
+"""Configuration system for AIVIDIO."""
+
+from aividio.config.profiles import ChannelProfile, load_profile
+from aividio.config.settings import Settings, get_settings
+
+__all__ = ["Settings", "get_settings", "ChannelProfile", "load_profile"]
diff --git a/src/vidmation/config/profiles.py b/src/aividio/config/profiles.py
similarity index 100%
rename from src/vidmation/config/profiles.py
rename to src/aividio/config/profiles.py
diff --git a/src/vidmation/config/settings.py b/src/aividio/config/settings.py
similarity index 95%
rename from src/vidmation/config/settings.py
rename to src/aividio/config/settings.py
index 3136153..f0532e2 100644
--- a/src/vidmation/config/settings.py
+++ b/src/aividio/config/settings.py
@@ -13,7 +13,7 @@
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
- env_prefix="VIDMATION_",
+ env_prefix="AIVIDIO_",
env_file_encoding="utf-8",
)
@@ -33,7 +33,7 @@ class Settings(BaseSettings):
pixabay_api_key: SecretStr = SecretStr("")
# --- Database ---
- database_url: str = "sqlite:///data/vidmation.db"
+ database_url: str = "sqlite:///data/aividio.db"
# --- Web ---
secret_key: SecretStr = SecretStr("change-me-in-production")
@@ -52,7 +52,7 @@ class Settings(BaseSettings):
# --- Notifications ---
email_provider: Literal["resend", "smtp"] = "resend"
- email_from: str = "noreply@vidmation.io"
+ email_from: str = "noreply@aividio.io"
email_to: str = "" # comma-separated
resend_api_key: SecretStr = SecretStr("")
smtp_host: str = ""
diff --git a/src/aividio/content/__init__.py b/src/aividio/content/__init__.py
new file mode 100644
index 0000000..6bdc9cf
--- /dev/null
+++ b/src/aividio/content/__init__.py
@@ -0,0 +1,7 @@
+"""Content planning and scheduling for automated video production."""
+
+from aividio.content.calendar import ContentCalendar
+from aividio.content.planner import ContentPlanner
+from aividio.content.series import SeriesManager
+
+__all__ = ["ContentPlanner", "ContentCalendar", "SeriesManager"]
diff --git a/src/vidmation/content/calendar.py b/src/aividio/content/calendar.py
similarity index 98%
rename from src/vidmation/content/calendar.py
rename to src/aividio/content/calendar.py
index dc76660..9df7aad 100644
--- a/src/vidmation/content/calendar.py
+++ b/src/aividio/content/calendar.py
@@ -9,7 +9,7 @@
from pathlib import Path
from typing import Any
-from vidmation.config.settings import get_settings
+from aividio.config.settings import get_settings
logger = logging.getLogger(__name__)
@@ -17,10 +17,10 @@
_VCALENDAR_HEADER = """\
BEGIN:VCALENDAR
VERSION:2.0
-PRODID:-//VIDMATION//Content Calendar//EN
+PRODID:-//AIVIDIO//Content Calendar//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
-X-WR-CALNAME:VIDMATION Content Calendar
+X-WR-CALNAME:AIVIDIO Content Calendar
"""
_VEVENT_TEMPLATE = """\
@@ -233,7 +233,7 @@ def enqueue_pending(self, limit: int | None = None) -> list[dict]:
that should be sent to the video generation queue.
This method does NOT commit to the database — the caller is
- responsible for calling :func:`vidmation.queue.tasks.enqueue_video`
+ responsible for calling :func:`aividio.queue.tasks.enqueue_video`
for each returned entry and then calling :meth:`save`.
"""
today = date.today().isoformat()
diff --git a/src/vidmation/content/planner.py b/src/aividio/content/planner.py
similarity index 97%
rename from src/vidmation/content/planner.py
rename to src/aividio/content/planner.py
index 909eae0..f897aa9 100644
--- a/src/vidmation/content/planner.py
+++ b/src/aividio/content/planner.py
@@ -9,11 +9,11 @@
import anthropic
-from vidmation.config.profiles import ChannelProfile, get_default_profile, load_profile
-from vidmation.config.settings import Settings, get_settings
-from vidmation.db.engine import get_session
-from vidmation.db.repos import ChannelRepo, VideoRepo
-from vidmation.utils.retry import retry
+from aividio.config.profiles import ChannelProfile, get_default_profile, load_profile
+from aividio.config.settings import Settings, get_settings
+from aividio.db.engine import get_session
+from aividio.db.repos import ChannelRepo, VideoRepo
+from aividio.utils.retry import retry
logger = logging.getLogger(__name__)
@@ -181,7 +181,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"anthropic_api_key is not configured. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
self._client = anthropic.Anthropic(api_key=api_key)
diff --git a/src/vidmation/content/series.py b/src/aividio/content/series.py
similarity index 99%
rename from src/vidmation/content/series.py
rename to src/aividio/content/series.py
index 49c1c54..3935693 100644
--- a/src/vidmation/content/series.py
+++ b/src/aividio/content/series.py
@@ -9,7 +9,7 @@
from pathlib import Path
from typing import Any
-from vidmation.config.settings import get_settings
+from aividio.config.settings import get_settings
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/db/__init__.py b/src/aividio/db/__init__.py
similarity index 60%
rename from src/vidmation/db/__init__.py
rename to src/aividio/db/__init__.py
index 68a52f4..6e9cf38 100644
--- a/src/vidmation/db/__init__.py
+++ b/src/aividio/db/__init__.py
@@ -1,5 +1,5 @@
"""Database engine and session management."""
-from vidmation.db.engine import get_engine, get_session, init_db
+from aividio.db.engine import get_engine, get_session, init_db
__all__ = ["get_engine", "get_session", "init_db"]
diff --git a/src/vidmation/db/engine.py b/src/aividio/db/engine.py
similarity index 91%
rename from src/vidmation/db/engine.py
rename to src/aividio/db/engine.py
index 2c5f45c..5008254 100644
--- a/src/vidmation/db/engine.py
+++ b/src/aividio/db/engine.py
@@ -7,8 +7,8 @@
from sqlalchemy import Engine, create_engine
from sqlalchemy.orm import Session, sessionmaker
-from vidmation.config.settings import get_settings
-from vidmation.models.base import Base
+from aividio.config.settings import get_settings
+from aividio.models.base import Base
@lru_cache
diff --git a/src/vidmation/db/repos.py b/src/aividio/db/repos.py
similarity index 96%
rename from src/vidmation/db/repos.py
rename to src/aividio/db/repos.py
index 906427a..b63b706 100644
--- a/src/vidmation/db/repos.py
+++ b/src/aividio/db/repos.py
@@ -5,9 +5,9 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
-from vidmation.models.channel import Channel
-from vidmation.models.job import Job, JobStatus
-from vidmation.models.video import Video, VideoStatus
+from aividio.models.channel import Channel
+from aividio.models.job import Job, JobStatus
+from aividio.models.video import Video, VideoStatus
class ChannelRepo:
diff --git a/src/aividio/effects/__init__.py b/src/aividio/effects/__init__.py
new file mode 100644
index 0000000..41dc105
--- /dev/null
+++ b/src/aividio/effects/__init__.py
@@ -0,0 +1,15 @@
+"""Post-production effects — auto-zoom, silence removal, B-roll, emoji/SFX, and magic clips."""
+
+from aividio.effects.emoji_sfx import EmojiSFXEngine
+from aividio.effects.magic_broll import MagicBRoll
+from aividio.effects.magic_clips import MagicClips
+from aividio.effects.magic_zoom import MagicZoom
+from aividio.effects.silence_remover import SilenceRemover
+
+__all__ = [
+ "EmojiSFXEngine",
+ "MagicBRoll",
+ "MagicClips",
+ "MagicZoom",
+ "SilenceRemover",
+]
diff --git a/src/vidmation/effects/emoji_sfx.py b/src/aividio/effects/emoji_sfx.py
similarity index 98%
rename from src/vidmation/effects/emoji_sfx.py
rename to src/aividio/effects/emoji_sfx.py
index 1ec770d..d3908ee 100644
--- a/src/vidmation/effects/emoji_sfx.py
+++ b/src/aividio/effects/emoji_sfx.py
@@ -15,9 +15,9 @@
import ffmpeg
-from vidmation.config.settings import Settings, get_settings
-from vidmation.utils.ffmpeg import FFmpegError, get_resolution, run_ffmpeg
-from vidmation.utils.retry import retry
+from aividio.config.settings import Settings, get_settings
+from aividio.utils.ffmpeg import FFmpegError, get_resolution, run_ffmpeg
+from aividio.utils.retry import retry
if TYPE_CHECKING:
pass
@@ -150,7 +150,7 @@ class EmojiSFXEngine:
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
- self.logger = logging.getLogger(f"vidmation.effects.{self.__class__.__name__}")
+ self.logger = logging.getLogger(f"aividio.effects.{self.__class__.__name__}")
self._sfx_dir = self.settings.assets_dir / "sfx"
# ------------------------------------------------------------------
@@ -585,7 +585,7 @@ def auto_enhance(
sfx_points = self.detect_sfx_points(word_timestamps, max_sfx=max_sfx)
if sfx_points:
# Extract audio, mix SFX, then recombine with video.
- with tempfile.TemporaryDirectory(prefix="vidmation_sfx_") as tmp_dir:
+ with tempfile.TemporaryDirectory(prefix="aividio_sfx_") as tmp_dir:
tmp_path = Path(tmp_dir)
extracted_audio = tmp_path / "audio.aac"
mixed_audio = tmp_path / "mixed_audio.aac"
diff --git a/src/vidmation/effects/magic_broll.py b/src/aividio/effects/magic_broll.py
similarity index 98%
rename from src/vidmation/effects/magic_broll.py
rename to src/aividio/effects/magic_broll.py
index 52d1562..d766aff 100644
--- a/src/vidmation/effects/magic_broll.py
+++ b/src/aividio/effects/magic_broll.py
@@ -15,9 +15,9 @@
import ffmpeg
-from vidmation.config.settings import Settings, get_settings
-from vidmation.utils.ffmpeg import FFmpegError, get_duration, get_resolution, run_ffmpeg
-from vidmation.utils.retry import retry
+from aividio.config.settings import Settings, get_settings
+from aividio.utils.ffmpeg import FFmpegError, get_duration, get_resolution, run_ffmpeg
+from aividio.utils.retry import retry
if TYPE_CHECKING:
pass
@@ -72,7 +72,7 @@ class MagicBRoll:
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
- self.logger = logging.getLogger(f"vidmation.effects.{self.__class__.__name__}")
+ self.logger = logging.getLogger(f"aividio.effects.{self.__class__.__name__}")
# ------------------------------------------------------------------
# Transcript analysis
@@ -261,7 +261,7 @@ def source_broll(
providers = ["pexels", "pixabay"]
if download_dir is None:
- download_dir = Path(tempfile.mkdtemp(prefix="vidmation_broll_"))
+ download_dir = Path(tempfile.mkdtemp(prefix="aividio_broll_"))
download_dir.mkdir(parents=True, exist_ok=True)
self.logger.info(
@@ -322,10 +322,10 @@ def _create_providers(self, provider_names: list[str]) -> list[Any]:
for name in provider_names:
try:
if name.lower() == "pexels":
- from vidmation.services.media.pexels import PexelsMediaProvider
+ from aividio.services.media.pexels import PexelsMediaProvider
providers.append(PexelsMediaProvider(settings=self.settings))
elif name.lower() == "pixabay":
- from vidmation.services.media.pixabay import PixabayMediaProvider
+ from aividio.services.media.pixabay import PixabayMediaProvider
providers.append(PixabayMediaProvider(settings=self.settings))
else:
self.logger.warning("Unknown media provider: %s", name)
diff --git a/src/vidmation/effects/magic_clips.py b/src/aividio/effects/magic_clips.py
similarity index 98%
rename from src/vidmation/effects/magic_clips.py
rename to src/aividio/effects/magic_clips.py
index 13c653f..168fd8d 100644
--- a/src/vidmation/effects/magic_clips.py
+++ b/src/aividio/effects/magic_clips.py
@@ -15,13 +15,13 @@
import ffmpeg
-from vidmation.config.settings import Settings, get_settings
-from vidmation.utils.ffmpeg import (
+from aividio.config.settings import Settings, get_settings
+from aividio.utils.ffmpeg import (
FFmpegError,
get_resolution,
run_ffmpeg,
)
-from vidmation.utils.retry import retry
+from aividio.utils.retry import retry
if TYPE_CHECKING:
pass
@@ -81,7 +81,7 @@ class MagicClips:
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
- self.logger = logging.getLogger(f"vidmation.effects.{self.__class__.__name__}")
+ self.logger = logging.getLogger(f"aividio.effects.{self.__class__.__name__}")
# ------------------------------------------------------------------
# Clip analysis
@@ -519,7 +519,7 @@ def _apply_clip_captions(
Filters word timestamps to the clip's time range, adjusts them to
start from 0, generates an ASS file, and burns it in.
"""
- from vidmation.video.captions_render import burn_captions, generate_ass_file
+ from aividio.video.captions_render import burn_captions, generate_ass_file
# Filter and offset word timestamps for this clip.
clip_words: list[dict] = []
@@ -547,7 +547,7 @@ def _apply_clip_captions(
animation = "karaoke" if caption_template == "karaoke" else "pop_in"
# Generate ASS file.
- with tempfile.TemporaryDirectory(prefix="vidmation_clip_captions_") as tmp_dir:
+ with tempfile.TemporaryDirectory(prefix="aividio_clip_captions_") as tmp_dir:
ass_path = Path(tmp_dir) / "clip_captions.ass"
generate_ass_file(
words=clip_words,
diff --git a/src/vidmation/effects/magic_zoom.py b/src/aividio/effects/magic_zoom.py
similarity index 98%
rename from src/vidmation/effects/magic_zoom.py
rename to src/aividio/effects/magic_zoom.py
index ad9dea3..c9a3b05 100644
--- a/src/vidmation/effects/magic_zoom.py
+++ b/src/aividio/effects/magic_zoom.py
@@ -15,15 +15,15 @@
import ffmpeg
-from vidmation.config.settings import Settings, get_settings
-from vidmation.utils.ffmpeg import (
+from aividio.config.settings import Settings, get_settings
+from aividio.utils.ffmpeg import (
FFmpegError,
get_duration,
get_frame_rate,
get_resolution,
run_ffmpeg,
)
-from vidmation.utils.retry import retry
+from aividio.utils.retry import retry
if TYPE_CHECKING:
pass
@@ -101,7 +101,7 @@ class MagicZoom:
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
- self.logger = logging.getLogger(f"vidmation.effects.{self.__class__.__name__}")
+ self.logger = logging.getLogger(f"aividio.effects.{self.__class__.__name__}")
# ------------------------------------------------------------------
# Emphasis detection
diff --git a/src/vidmation/effects/silence_remover.py b/src/aividio/effects/silence_remover.py
similarity index 99%
rename from src/vidmation/effects/silence_remover.py
rename to src/aividio/effects/silence_remover.py
index d47c7f0..05732ce 100644
--- a/src/vidmation/effects/silence_remover.py
+++ b/src/aividio/effects/silence_remover.py
@@ -12,7 +12,7 @@
import re
from pathlib import Path
-from vidmation.utils.ffmpeg import FFmpegError, get_duration, run_ffmpeg
+from aividio.utils.ffmpeg import FFmpegError, get_duration, run_ffmpeg
logger = logging.getLogger(__name__)
@@ -43,7 +43,7 @@ class SilenceRemover:
}
def __init__(self) -> None:
- self.logger = logging.getLogger(f"vidmation.effects.{self.__class__.__name__}")
+ self.logger = logging.getLogger(f"aividio.effects.{self.__class__.__name__}")
# ------------------------------------------------------------------
# Silence detection
diff --git a/src/aividio/models/__init__.py b/src/aividio/models/__init__.py
new file mode 100644
index 0000000..d6c38f7
--- /dev/null
+++ b/src/aividio/models/__init__.py
@@ -0,0 +1,51 @@
+"""Database models for AIVIDIO."""
+
+from aividio.models.analytics import (
+ CostSummary,
+ OperationType,
+ PeriodType,
+ ServiceType,
+ UsageEvent,
+ VideoAnalytics,
+)
+from aividio.models.api_key import APIKey
+from aividio.models.asset import Asset, AssetSource, AssetType
+from aividio.models.base import Base
+from aividio.models.channel import Channel
+from aividio.models.job import Job, JobStatus, JobType
+from aividio.models.notification import Notification
+from aividio.models.schedule import Schedule, ScheduleStatus, ScheduleType, TopicSource
+from aividio.models.user import SubscriptionTier, User
+from aividio.models.video import Video, VideoFormat, VideoStatus
+from aividio.models.voice import Voice
+from aividio.models.webhook import Webhook
+
+__all__ = [
+ "Base",
+ "Channel",
+ "Video",
+ "VideoFormat",
+ "VideoStatus",
+ "Job",
+ "JobStatus",
+ "JobType",
+ "Asset",
+ "AssetType",
+ "AssetSource",
+ "APIKey",
+ "Webhook",
+ "Notification",
+ "Schedule",
+ "ScheduleStatus",
+ "ScheduleType",
+ "TopicSource",
+ "UsageEvent",
+ "CostSummary",
+ "VideoAnalytics",
+ "ServiceType",
+ "OperationType",
+ "PeriodType",
+ "Voice",
+ "User",
+ "SubscriptionTier",
+]
diff --git a/src/vidmation/models/analytics.py b/src/aividio/models/analytics.py
similarity index 98%
rename from src/vidmation/models/analytics.py
rename to src/aividio/models/analytics.py
index 2765ee9..27965f2 100644
--- a/src/vidmation/models/analytics.py
+++ b/src/aividio/models/analytics.py
@@ -8,7 +8,7 @@
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class ServiceType(str, enum.Enum):
diff --git a/src/vidmation/models/api_key.py b/src/aividio/models/api_key.py
similarity index 93%
rename from src/vidmation/models/api_key.py
rename to src/aividio/models/api_key.py
index c2e9fd8..4f70ec4 100644
--- a/src/vidmation/models/api_key.py
+++ b/src/aividio/models/api_key.py
@@ -7,7 +7,7 @@
from sqlalchemy import Boolean, DateTime, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class APIKey(Base, UUIDMixin, TimestampMixin):
diff --git a/src/vidmation/models/asset.py b/src/aividio/models/asset.py
similarity index 97%
rename from src/vidmation/models/asset.py
rename to src/aividio/models/asset.py
index 09e8143..396cfa0 100644
--- a/src/vidmation/models/asset.py
+++ b/src/aividio/models/asset.py
@@ -14,7 +14,7 @@
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class AssetType(str, enum.Enum):
diff --git a/src/vidmation/models/base.py b/src/aividio/models/base.py
similarity index 100%
rename from src/vidmation/models/base.py
rename to src/aividio/models/base.py
diff --git a/src/vidmation/models/channel.py b/src/aividio/models/channel.py
similarity index 95%
rename from src/vidmation/models/channel.py
rename to src/aividio/models/channel.py
index 0468b23..c7a000a 100644
--- a/src/vidmation/models/channel.py
+++ b/src/aividio/models/channel.py
@@ -7,7 +7,7 @@
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class Channel(Base, UUIDMixin, TimestampMixin):
diff --git a/src/vidmation/models/job.py b/src/aividio/models/job.py
similarity index 96%
rename from src/vidmation/models/job.py
rename to src/aividio/models/job.py
index f404135..ffa2b6c 100644
--- a/src/vidmation/models/job.py
+++ b/src/aividio/models/job.py
@@ -8,7 +8,7 @@
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class JobType(str, enum.Enum):
diff --git a/src/vidmation/models/notification.py b/src/aividio/models/notification.py
similarity index 96%
rename from src/vidmation/models/notification.py
rename to src/aividio/models/notification.py
index a2d5697..3c83645 100644
--- a/src/vidmation/models/notification.py
+++ b/src/aividio/models/notification.py
@@ -8,7 +8,7 @@
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import Mapped, mapped_column
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class Notification(Base, UUIDMixin, TimestampMixin):
diff --git a/src/vidmation/models/schedule.py b/src/aividio/models/schedule.py
similarity index 97%
rename from src/vidmation/models/schedule.py
rename to src/aividio/models/schedule.py
index 00b6167..9665427 100644
--- a/src/vidmation/models/schedule.py
+++ b/src/aividio/models/schedule.py
@@ -9,7 +9,7 @@
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class ScheduleType(str, enum.Enum):
diff --git a/src/vidmation/models/user.py b/src/aividio/models/user.py
similarity index 98%
rename from src/vidmation/models/user.py
rename to src/aividio/models/user.py
index 4ac56c2..3f791b4 100644
--- a/src/vidmation/models/user.py
+++ b/src/aividio/models/user.py
@@ -8,7 +8,7 @@
from sqlalchemy import Boolean, DateTime, Enum, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class SubscriptionTier(str, enum.Enum):
diff --git a/src/vidmation/models/video.py b/src/aividio/models/video.py
similarity index 97%
rename from src/vidmation/models/video.py
rename to src/aividio/models/video.py
index 0af0f95..e1a7dfd 100644
--- a/src/vidmation/models/video.py
+++ b/src/aividio/models/video.py
@@ -8,7 +8,7 @@
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class VideoFormat(str, enum.Enum):
diff --git a/src/vidmation/models/voice.py b/src/aividio/models/voice.py
similarity index 97%
rename from src/vidmation/models/voice.py
rename to src/aividio/models/voice.py
index b0d939e..976e802 100644
--- a/src/vidmation/models/voice.py
+++ b/src/aividio/models/voice.py
@@ -5,7 +5,7 @@
from sqlalchemy import JSON, Boolean, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class Voice(Base, UUIDMixin, TimestampMixin):
diff --git a/src/vidmation/models/webhook.py b/src/aividio/models/webhook.py
similarity index 94%
rename from src/vidmation/models/webhook.py
rename to src/aividio/models/webhook.py
index 859baa0..c20fe9e 100644
--- a/src/vidmation/models/webhook.py
+++ b/src/aividio/models/webhook.py
@@ -8,7 +8,7 @@
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import Mapped, mapped_column
-from vidmation.models.base import Base, TimestampMixin, UUIDMixin
+from aividio.models.base import Base, TimestampMixin, UUIDMixin
class Webhook(Base, UUIDMixin, TimestampMixin):
diff --git a/src/vidmation/notifications/__init__.py b/src/aividio/notifications/__init__.py
similarity index 57%
rename from src/vidmation/notifications/__init__.py
rename to src/aividio/notifications/__init__.py
index a84f6df..87d4a25 100644
--- a/src/vidmation/notifications/__init__.py
+++ b/src/aividio/notifications/__init__.py
@@ -1,9 +1,9 @@
-"""Multi-channel notification system for VIDMATION.
+"""Multi-channel notification system for AIVIDIO.
Supports email (Resend/SMTP), Discord webhooks, Slack webhooks,
and in-app notifications stored in the database.
"""
-from vidmation.notifications.manager import NotificationManager
+from aividio.notifications.manager import NotificationManager
__all__ = ["NotificationManager"]
diff --git a/src/vidmation/notifications/discord.py b/src/aividio/notifications/discord.py
similarity index 93%
rename from src/vidmation/notifications/discord.py
rename to src/aividio/notifications/discord.py
index 1ce6809..635dc4a 100644
--- a/src/vidmation/notifications/discord.py
+++ b/src/aividio/notifications/discord.py
@@ -1,7 +1,7 @@
"""Discord webhook notifier — sends embed-formatted messages.
Configuration:
- VIDMATION_DISCORD_WEBHOOK_URL: full Discord webhook URL
+ AIVIDIO_DISCORD_WEBHOOK_URL: full Discord webhook URL
"""
from __future__ import annotations
@@ -10,7 +10,7 @@
import os
from typing import Any
-logger = logging.getLogger("vidmation.notifications.discord")
+logger = logging.getLogger("aividio.notifications.discord")
# Event-to-colour mapping (Discord embed colour is an integer).
_EVENT_COLOURS: dict[str, int] = {
@@ -42,7 +42,7 @@ class DiscordNotifier:
"""
def __init__(self) -> None:
- self.webhook_url = os.getenv("VIDMATION_DISCORD_WEBHOOK_URL", "")
+ self.webhook_url = os.getenv("AIVIDIO_DISCORD_WEBHOOK_URL", "")
@property
def is_configured(self) -> bool:
@@ -65,8 +65,8 @@ def send(
embed = self._build_embed(event, title, message, data)
payload: dict[str, Any] = {
- "username": "VIDMATION",
- "avatar_url": "https://vidmation.io/static/images/logo.png",
+ "username": "AIVIDIO",
+ "avatar_url": "https://aividio.io/static/images/logo.png",
"embeds": [embed],
}
@@ -108,7 +108,7 @@ def _build_embed(
"description": message,
"color": colour,
"footer": {
- "text": f"VIDMATION | Event: {event}",
+ "text": f"AIVIDIO | Event: {event}",
},
}
diff --git a/src/vidmation/notifications/email.py b/src/aividio/notifications/email.py
similarity index 55%
rename from src/vidmation/notifications/email.py
rename to src/aividio/notifications/email.py
index 336fe73..d187216 100644
--- a/src/vidmation/notifications/email.py
+++ b/src/aividio/notifications/email.py
@@ -1,11 +1,11 @@
"""Email notifier — sends HTML-formatted notifications via Resend or SMTP.
-Configuration (env vars with VIDMATION_ prefix):
- VIDMATION_EMAIL_PROVIDER: "resend" | "smtp" (default: "resend")
- VIDMATION_EMAIL_FROM: sender address
- VIDMATION_EMAIL_TO: comma-separated recipient addresses
- VIDMATION_RESEND_API_KEY: Resend API key (if provider=resend)
- VIDMATION_SMTP_HOST / _PORT / _USER / _PASSWORD: SMTP config
+Configuration (env vars with AIVIDIO_ prefix):
+ AIVIDIO_EMAIL_PROVIDER: "resend" | "smtp" (default: "resend")
+ AIVIDIO_EMAIL_FROM: sender address
+ AIVIDIO_EMAIL_TO: comma-separated recipient addresses
+ AIVIDIO_RESEND_API_KEY: Resend API key (if provider=resend)
+ AIVIDIO_SMTP_HOST / _PORT / _USER / _PASSWORD: SMTP config
"""
from __future__ import annotations
@@ -16,7 +16,7 @@
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
-logger = logging.getLogger("vidmation.notifications.email")
+logger = logging.getLogger("aividio.notifications.email")
# ---------------------------------------------------------------------------
@@ -51,8 +51,8 @@
{style}
{title}
@@ -62,7 +62,7 @@
Video Ready
-
+
""",
@@ -70,8 +70,8 @@
{style}
{title}
@@ -81,7 +81,7 @@
Job Failed
-
+
""",
@@ -89,8 +89,8 @@
{style}
{title}
@@ -100,7 +100,7 @@
Upload Complete
-
+
""",
@@ -108,8 +108,8 @@
{style}
{title}
@@ -119,7 +119,45 @@
Cost Alert
-
+
+
+
+""",
+ "password_reset": """
+{style}
+
+
+
+
{title}
+
{message}
+ {extra}
+
+
+ Password Reset
+
+
+
+
+""",
+ "payment_failed": """
+{style}
+
+
+
+
{title}
+
{message}
+ {extra}
+
+
+ Action Required
+
+
""",
@@ -127,15 +165,15 @@
{style}
{title}
{message}
{extra}
-
+
""",
@@ -148,6 +186,14 @@ def _render_html(event: str, title: str, message: str, data: dict | None = None)
extra_parts: list[str] = []
if data:
+ if "reset_url" in data:
+ extra_parts.append(
+ f'
Reset Password
'
+ )
+ if "billing_url" in data:
+ extra_parts.append(
+ f'
Update Payment Method
'
+ )
if "youtube_url" in data:
extra_parts.append(
f'
View on YouTube
'
@@ -181,22 +227,22 @@ def _render_html(event: str, title: str, message: str, data: dict | None = None)
class EmailNotifier:
"""Send email notifications via Resend API or SMTP fallback.
- Reads configuration from environment variables (prefixed VIDMATION_).
+ Reads configuration from environment variables (prefixed AIVIDIO_).
"""
def __init__(self) -> None:
- self.provider = os.getenv("VIDMATION_EMAIL_PROVIDER", "resend").lower()
- self.from_address = os.getenv("VIDMATION_EMAIL_FROM", "noreply@vidmation.io")
+ self.provider = os.getenv("AIVIDIO_EMAIL_PROVIDER", "resend").lower()
+ self.from_address = os.getenv("AIVIDIO_EMAIL_FROM", "noreply@aividio.io")
self.to_addresses = [
addr.strip()
- for addr in os.getenv("VIDMATION_EMAIL_TO", "").split(",")
+ for addr in os.getenv("AIVIDIO_EMAIL_TO", "").split(",")
if addr.strip()
]
- self._resend_api_key = os.getenv("VIDMATION_RESEND_API_KEY", "")
- self._smtp_host = os.getenv("VIDMATION_SMTP_HOST", "")
- self._smtp_port = int(os.getenv("VIDMATION_SMTP_PORT", "587"))
- self._smtp_user = os.getenv("VIDMATION_SMTP_USER", "")
- self._smtp_password = os.getenv("VIDMATION_SMTP_PASSWORD", "")
+ self._resend_api_key = os.getenv("AIVIDIO_RESEND_API_KEY", "")
+ self._smtp_host = os.getenv("AIVIDIO_SMTP_HOST", "")
+ self._smtp_port = int(os.getenv("AIVIDIO_SMTP_PORT", "587"))
+ self._smtp_user = os.getenv("AIVIDIO_SMTP_USER", "")
+ self._smtp_password = os.getenv("AIVIDIO_SMTP_PASSWORD", "")
@property
def is_configured(self) -> bool:
@@ -233,10 +279,55 @@ def send(
logger.error("Failed to send email notification", exc_info=True)
return False
- def _send_resend(self, subject: str, html_body: str) -> bool:
+ @property
+ def can_send_transactional(self) -> bool:
+ """Check if the notifier can send transactional emails (to arbitrary recipients)."""
+ if self.provider == "resend":
+ return bool(self._resend_api_key)
+ return bool(self._smtp_host)
+
+ def send_to(
+ self,
+ to: str | list[str],
+ subject: str,
+ event: str,
+ title: str,
+ message: str,
+ data: dict | None = None,
+ ) -> bool:
+ """Send a transactional email to a specific recipient.
+
+ Unlike ``send()``, this does not require ``AIVIDIO_EMAIL_TO`` to be
+ configured — the recipient is passed explicitly.
+
+ Returns True on success, False on failure.
+ """
+ if not self.can_send_transactional:
+ logger.debug("Email provider not configured, skipping transactional email")
+ return False
+
+ recipients = [to] if isinstance(to, str) else to
+ html_body = _render_html(event, title, message, data)
+
+ try:
+ if self.provider == "resend":
+ return self._send_resend(subject, html_body, recipients)
+ else:
+ return self._send_smtp(subject, html_body, recipients)
+ except Exception:
+ logger.error("Failed to send transactional email to %s", recipients, exc_info=True)
+ return False
+
+ def _send_resend(
+ self,
+ subject: str,
+ html_body: str,
+ recipients: list[str] | None = None,
+ ) -> bool:
"""Send via Resend HTTP API."""
import httpx
+ to = recipients or self.to_addresses
response = httpx.post(
"https://api.resend.com/emails",
headers={
@@ -245,14 +336,14 @@ def _send_resend(self, subject: str, html_body: str) -> bool:
},
json={
"from": self.from_address,
- "to": self.to_addresses,
- "subject": f"[VIDMATION] {subject}",
+ "to": to,
+ "subject": f"[AIVIDIO] {subject}",
"html": html_body,
},
timeout=15.0,
)
if response.status_code in (200, 201):
- logger.info("Email sent via Resend to %s", self.to_addresses)
+ logger.info("Email sent via Resend to %s", to)
return True
logger.error(
@@ -260,12 +351,18 @@ def _send_resend(self, subject: str, html_body: str) -> bool:
)
return False
- def _send_smtp(self, subject: str, html_body: str) -> bool:
+ def _send_smtp(
+ self,
+ subject: str,
+ html_body: str,
+ recipients: list[str] | None = None,
+ ) -> bool:
"""Send via SMTP (TLS)."""
+ to = recipients or self.to_addresses
msg = MIMEMultipart("alternative")
- msg["Subject"] = f"[VIDMATION] {subject}"
+ msg["Subject"] = f"[AIVIDIO] {subject}"
msg["From"] = self.from_address
- msg["To"] = ", ".join(self.to_addresses)
+ msg["To"] = ", ".join(to)
msg.attach(MIMEText(html_body, "html"))
with smtplib.SMTP(self._smtp_host, self._smtp_port) as server:
@@ -274,7 +371,46 @@ def _send_smtp(self, subject: str, html_body: str) -> bool:
server.ehlo()
if self._smtp_user:
server.login(self._smtp_user, self._smtp_password)
- server.sendmail(self.from_address, self.to_addresses, msg.as_string())
+ server.sendmail(self.from_address, to, msg.as_string())
- logger.info("Email sent via SMTP to %s", self.to_addresses)
+ logger.info("Email sent via SMTP to %s", to)
return True
+
+
+# ---------------------------------------------------------------------------
+# Transactional email helpers
+# ---------------------------------------------------------------------------
+
+
+def send_password_reset_email(to: str, reset_url: str) -> bool:
+ """Send a password reset email to a user."""
+ notifier = EmailNotifier()
+ return notifier.send_to(
+ to=to,
+ subject="Reset Your Password",
+ event="password_reset",
+ title="Password Reset Request",
+ message=(
+ "We received a request to reset your password. "
+ "Click the button below to choose a new password. "
+ "This link expires in 1 hour."
+ ),
+ data={"reset_url": reset_url},
+ )
+
+
+def send_payment_failed_email(to: str, billing_url: str = "https://aividio.com/billing") -> bool:
+ """Send a dunning email when a payment fails."""
+ notifier = EmailNotifier()
+ return notifier.send_to(
+ to=to,
+ subject="Payment Failed - Action Required",
+ event="payment_failed",
+ title="Payment Failed",
+ message=(
+ "We were unable to process your latest payment. "
+ "Please update your payment method to continue using AIVIDIO. "
+ "Your subscription will remain active for a grace period."
+ ),
+ data={"billing_url": billing_url},
+ )
diff --git a/src/vidmation/notifications/manager.py b/src/aividio/notifications/manager.py
similarity index 96%
rename from src/vidmation/notifications/manager.py
rename to src/aividio/notifications/manager.py
index 935928f..2723cfc 100644
--- a/src/vidmation/notifications/manager.py
+++ b/src/aividio/notifications/manager.py
@@ -2,7 +2,7 @@
Usage::
- from vidmation.notifications.manager import NotificationManager
+ from aividio.notifications.manager import NotificationManager
notifier = NotificationManager()
notifier.notify_video_complete(video_id="abc123")
@@ -14,13 +14,13 @@
import logging
from datetime import datetime, timezone
-from vidmation.db.engine import get_session
-from vidmation.models.notification import Notification
-from vidmation.notifications.discord import DiscordNotifier
-from vidmation.notifications.email import EmailNotifier
-from vidmation.notifications.slack import SlackNotifier
+from aividio.db.engine import get_session
+from aividio.models.notification import Notification
+from aividio.notifications.discord import DiscordNotifier
+from aividio.notifications.email import EmailNotifier
+from aividio.notifications.slack import SlackNotifier
-logger = logging.getLogger("vidmation.notifications.manager")
+logger = logging.getLogger("aividio.notifications.manager")
class NotificationManager:
diff --git a/src/vidmation/notifications/slack.py b/src/aividio/notifications/slack.py
similarity index 95%
rename from src/vidmation/notifications/slack.py
rename to src/aividio/notifications/slack.py
index 8260f6e..5330029 100644
--- a/src/vidmation/notifications/slack.py
+++ b/src/aividio/notifications/slack.py
@@ -1,7 +1,7 @@
"""Slack webhook notifier — sends Block Kit formatted messages.
Configuration:
- VIDMATION_SLACK_WEBHOOK_URL: full Slack incoming webhook URL
+ AIVIDIO_SLACK_WEBHOOK_URL: full Slack incoming webhook URL
"""
from __future__ import annotations
@@ -10,7 +10,7 @@
import os
from typing import Any
-logger = logging.getLogger("vidmation.notifications.slack")
+logger = logging.getLogger("aividio.notifications.slack")
_EVENT_EMOJIS: dict[str, str] = {
"video_complete": ":movie_camera:",
@@ -41,7 +41,7 @@ class SlackNotifier:
"""
def __init__(self) -> None:
- self.webhook_url = os.getenv("VIDMATION_SLACK_WEBHOOK_URL", "")
+ self.webhook_url = os.getenv("AIVIDIO_SLACK_WEBHOOK_URL", "")
@property
def is_configured(self) -> bool:
@@ -176,7 +176,7 @@ def _build_payload(
"elements": [
{
"type": "mrkdwn",
- "text": f":zap: *VIDMATION* | Event: `{event}`",
+ "text": f":zap: *AIVIDIO* | Event: `{event}`",
}
],
})
diff --git a/src/aividio/pipeline/__init__.py b/src/aividio/pipeline/__init__.py
new file mode 100644
index 0000000..8abaca1
--- /dev/null
+++ b/src/aividio/pipeline/__init__.py
@@ -0,0 +1,6 @@
+"""Pipeline orchestration for AIVIDIO video generation."""
+
+from aividio.pipeline.context import PipelineContext
+from aividio.pipeline.orchestrator import PipelineOrchestrator
+
+__all__ = ["PipelineContext", "PipelineOrchestrator"]
diff --git a/src/vidmation/pipeline/context.py b/src/aividio/pipeline/context.py
similarity index 96%
rename from src/vidmation/pipeline/context.py
rename to src/aividio/pipeline/context.py
index d4fb879..2c9d08a 100644
--- a/src/vidmation/pipeline/context.py
+++ b/src/aividio/pipeline/context.py
@@ -7,8 +7,8 @@
from pathlib import Path
from typing import Any
-from vidmation.config.profiles import ChannelProfile
-from vidmation.models.video import VideoFormat
+from aividio.config.profiles import ChannelProfile
+from aividio.models.video import VideoFormat
@dataclass
diff --git a/src/vidmation/pipeline/orchestrator.py b/src/aividio/pipeline/orchestrator.py
similarity index 95%
rename from src/vidmation/pipeline/orchestrator.py
rename to src/aividio/pipeline/orchestrator.py
index e0e2650..d533ec4 100644
--- a/src/vidmation/pipeline/orchestrator.py
+++ b/src/aividio/pipeline/orchestrator.py
@@ -7,15 +7,15 @@
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Callable
-from vidmation.config.settings import Settings, get_settings
-from vidmation.db.engine import get_session
-from vidmation.db.repos import JobRepo, VideoRepo
-from vidmation.models.job import JobStatus
-from vidmation.models.video import VideoStatus
-from vidmation.pipeline.stages import STAGE_REGISTRY
+from aividio.config.settings import Settings, get_settings
+from aividio.db.engine import get_session
+from aividio.db.repos import JobRepo, VideoRepo
+from aividio.models.job import JobStatus
+from aividio.models.video import VideoStatus
+from aividio.pipeline.stages import STAGE_REGISTRY
if TYPE_CHECKING:
- from vidmation.pipeline.context import PipelineContext
+ from aividio.pipeline.context import PipelineContext
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/pipeline/stages.py b/src/aividio/pipeline/stages.py
similarity index 95%
rename from src/vidmation/pipeline/stages.py
rename to src/aividio/pipeline/stages.py
index 8d511ba..c576190 100644
--- a/src/vidmation/pipeline/stages.py
+++ b/src/aividio/pipeline/stages.py
@@ -14,8 +14,8 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
- from vidmation.pipeline.context import PipelineContext
+ from aividio.config.settings import Settings
+ from aividio.pipeline.context import PipelineContext
logger = logging.getLogger(__name__)
@@ -26,7 +26,7 @@
def stage_script_generation(ctx: PipelineContext, settings: Settings) -> None:
"""Generate a structured video script from the topic and channel profile."""
- from vidmation.services.scriptgen import create_script_generator
+ from aividio.services.scriptgen import create_script_generator
logger.info("[script] Generating script for topic=%r", ctx.topic)
@@ -53,7 +53,7 @@ def stage_script_generation(ctx: PipelineContext, settings: Settings) -> None:
def stage_tts(ctx: PipelineContext, settings: Settings) -> None:
"""Generate voiceover audio from the script narration."""
- from vidmation.services.tts import create_tts_provider
+ from aividio.services.tts import create_tts_provider
if ctx.script is None:
raise RuntimeError("stage_tts requires ctx.script to be populated")
@@ -97,7 +97,7 @@ def stage_tts(ctx: PipelineContext, settings: Settings) -> None:
def stage_captions(ctx: PipelineContext, settings: Settings) -> None:
"""Run Whisper on the voiceover to generate word-level timestamps."""
- from vidmation.services.captions.whisper import WhisperCaptionGenerator
+ from aividio.services.captions.whisper import WhisperCaptionGenerator
if ctx.voiceover_path is None:
raise RuntimeError("stage_captions requires ctx.voiceover_path")
@@ -132,7 +132,7 @@ def stage_music_selection(ctx: PipelineContext, settings: Settings) -> None:
Sets ``ctx.music_path`` so the video assembly stage can mix it with
the voiceover.
"""
- from vidmation.services.music.selector import MusicSelector
+ from aividio.services.music.selector import MusicSelector
logger.info("[music] Selecting background music (genre=%s)", ctx.channel_profile.music.genre)
@@ -168,7 +168,7 @@ def stage_ai_video_generation(ctx: PipelineContext, settings: Settings) -> None:
2. Profile-level override via ``ctx.channel_profile.content`` extra attrs
3. Default: ``"stock"`` (preserves backward compatibility)
"""
- from vidmation.services.models.orchestrator import ModelOrchestrator
+ from aividio.services.models.orchestrator import ModelOrchestrator
if ctx.script is None:
raise RuntimeError("stage_ai_video_generation requires ctx.script")
@@ -348,7 +348,7 @@ def stage_media_sourcing(ctx: PipelineContext, settings: Settings) -> None:
- ``"path"`` — the first clip (backward-compatible single path)
- ``"paths"`` — a list of all clip paths for multi-clip assembly
"""
- from vidmation.services.media import create_media_provider
+ from aividio.services.media import create_media_provider
if ctx.script is None:
raise RuntimeError("stage_media_sourcing requires ctx.script")
@@ -441,7 +441,7 @@ def stage_media_sourcing(ctx: PipelineContext, settings: Settings) -> None:
def stage_video_assembly(ctx: PipelineContext, settings: Settings) -> None:
"""Assemble voiceover, media clips, captions, and music into the final video."""
- from vidmation.utils.files import get_output_path
+ from aividio.utils.files import get_output_path
if ctx.voiceover_path is None:
raise RuntimeError("stage_video_assembly requires ctx.voiceover_path")
@@ -450,8 +450,8 @@ def stage_video_assembly(ctx: PipelineContext, settings: Settings) -> None:
logger.info("[assembly] Assembling final video")
- from vidmation.utils.files import get_work_dir
- from vidmation.video.assembler import VideoAssembler
+ from aividio.utils.files import get_work_dir
+ from aividio.video.assembler import VideoAssembler
output_path = get_output_path(ctx.video_id, "final.mp4")
work_dir = get_work_dir(ctx.video_id)
@@ -505,7 +505,7 @@ def stage_video_assembly(ctx: PipelineContext, settings: Settings) -> None:
def stage_thumbnail(ctx: PipelineContext, settings: Settings) -> None:
"""Generate a thumbnail image for the video."""
- from vidmation.services.imagegen import create_image_generator
+ from aividio.services.imagegen import create_image_generator
if ctx.script is None:
raise RuntimeError("stage_thumbnail requires ctx.script")
@@ -543,11 +543,11 @@ def stage_upload(ctx: PipelineContext, settings: Settings) -> None:
2. Global token file at ``data/youtube_token.json`` (legacy single-
channel mode).
"""
- from vidmation.services.youtube.auth import (
+ from aividio.services.youtube.auth import (
get_credentials,
get_credentials_for_channel,
)
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.uploader import YouTubeUploader
if ctx.final_video_path is None:
raise RuntimeError("stage_upload requires ctx.final_video_path")
@@ -565,8 +565,8 @@ def stage_upload(ctx: PipelineContext, settings: Settings) -> None:
channel_id = getattr(ctx, "channel_id", None)
if channel_id:
try:
- from vidmation.db.engine import get_session
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import ChannelRepo
session = get_session()
repo = ChannelRepo(session)
@@ -597,7 +597,7 @@ def stage_upload(ctx: PipelineContext, settings: Settings) -> None:
if not token_path.exists():
logger.warning(
"[upload] YouTube token not found at %s — skipping upload. "
- "Run 'vidmation youtube setup' to configure.",
+ "Run 'aividio youtube setup' to configure.",
token_path,
)
return
@@ -616,7 +616,7 @@ def stage_upload(ctx: PipelineContext, settings: Settings) -> None:
tags = ctx.script.get("tags", [])
try:
- from vidmation.services.youtube.metadata import YouTubeMetadataGenerator
+ from aividio.services.youtube.metadata import YouTubeMetadataGenerator
meta_gen = YouTubeMetadataGenerator(settings=settings)
metadata = meta_gen.generate(
@@ -634,7 +634,7 @@ def stage_upload(ctx: PipelineContext, settings: Settings) -> None:
publish_at = None
if yt_config.schedule:
try:
- from vidmation.cli.youtube import _parse_schedule
+ from aividio.cli.youtube import _parse_schedule
publish_at = _parse_schedule(yt_config.schedule)
logger.info("[upload] Scheduled publish at %s", publish_at.isoformat())
diff --git a/src/vidmation/platforms/__init__.py b/src/aividio/platforms/__init__.py
similarity index 58%
rename from src/vidmation/platforms/__init__.py
rename to src/aividio/platforms/__init__.py
index d9a6eb3..8059bb8 100644
--- a/src/vidmation/platforms/__init__.py
+++ b/src/aividio/platforms/__init__.py
@@ -1,4 +1,4 @@
-"""Multi-platform export support for VIDMATION.
+"""Multi-platform export support for AIVIDIO.
Submodules:
- base: Abstract base class defining the platform interface.
@@ -8,11 +8,11 @@
- exporter: :class:`MultiPlatformExporter` that orchestrates cross-platform output.
"""
-from vidmation.platforms.base import Platform, PlatformType
-from vidmation.platforms.exporter import MultiPlatformExporter
-from vidmation.platforms.instagram import InstagramPlatform
-from vidmation.platforms.tiktok import TikTokPlatform
-from vidmation.platforms.youtube import YouTubePlatform
+from aividio.platforms.base import Platform, PlatformType
+from aividio.platforms.exporter import MultiPlatformExporter
+from aividio.platforms.instagram import InstagramPlatform
+from aividio.platforms.tiktok import TikTokPlatform
+from aividio.platforms.youtube import YouTubePlatform
__all__ = [
"MultiPlatformExporter",
diff --git a/src/vidmation/platforms/base.py b/src/aividio/platforms/base.py
similarity index 97%
rename from src/vidmation/platforms/base.py
rename to src/aividio/platforms/base.py
index 49f7c84..5c9bb20 100644
--- a/src/vidmation/platforms/base.py
+++ b/src/aividio/platforms/base.py
@@ -45,7 +45,7 @@ class Platform(ABC):
def __init__(self) -> None:
self.logger = logging.getLogger(
- f"vidmation.platforms.{self.__class__.__name__}"
+ f"aividio.platforms.{self.__class__.__name__}"
)
# ------------------------------------------------------------------
@@ -74,7 +74,7 @@ def format_for_platform(
Raises:
FileNotFoundError: If *video_path* does not exist.
- vidmation.utils.ffmpeg.FFmpegError: On encoding failure.
+ aividio.utils.ffmpeg.FFmpegError: On encoding failure.
"""
...
diff --git a/src/vidmation/platforms/exporter.py b/src/aividio/platforms/exporter.py
similarity index 93%
rename from src/vidmation/platforms/exporter.py
rename to src/aividio/platforms/exporter.py
index f17b018..0ebd17b 100644
--- a/src/vidmation/platforms/exporter.py
+++ b/src/aividio/platforms/exporter.py
@@ -12,13 +12,13 @@
from pathlib import Path
from typing import Any
-from vidmation.config.profiles import ChannelProfile
-from vidmation.platforms.base import Platform
-from vidmation.platforms.facebook import FacebookPlatform
-from vidmation.platforms.instagram import InstagramPlatform
-from vidmation.platforms.tiktok import TikTokPlatform
-from vidmation.platforms.twitter import TwitterPlatform
-from vidmation.platforms.youtube import YouTubePlatform
+from aividio.config.profiles import ChannelProfile
+from aividio.platforms.base import Platform
+from aividio.platforms.facebook import FacebookPlatform
+from aividio.platforms.instagram import InstagramPlatform
+from aividio.platforms.tiktok import TikTokPlatform
+from aividio.platforms.twitter import TwitterPlatform
+from aividio.platforms.youtube import YouTubePlatform
logger = logging.getLogger(__name__)
@@ -107,13 +107,13 @@ def __init__(
Parameters:
output_dir: Base directory for all platform-specific outputs. If
``None``, outputs are placed alongside the source video.
- brand_kit: Optional :class:`~vidmation.brand.kit.BrandKit` instance.
+ brand_kit: Optional :class:`~aividio.brand.kit.BrandKit` instance.
When provided, the brand kit's intro, outro, logo, and watermark
are applied before platform reformatting.
"""
self.output_dir = Path(output_dir) if output_dir else None
self.brand_kit = brand_kit
- self.logger = logging.getLogger("vidmation.platforms.MultiPlatformExporter")
+ self.logger = logging.getLogger("aividio.platforms.MultiPlatformExporter")
def export(
self,
@@ -261,7 +261,7 @@ def _apply_brand_kit(self, video_path: Path) -> Path:
try:
# Lazy import to avoid circular dependency
- from vidmation.brand.kit import BrandKit
+ from aividio.brand.kit import BrandKit
if isinstance(self.brand_kit, BrandKit):
out_dir = self.output_dir or video_path.parent
@@ -271,7 +271,7 @@ def _apply_brand_kit(self, video_path: Path) -> Path:
return result
except ImportError:
self.logger.warning(
- "Brand kit provided but vidmation.brand module not available"
+ "Brand kit provided but aividio.brand module not available"
)
return video_path
diff --git a/src/vidmation/platforms/facebook.py b/src/aividio/platforms/facebook.py
similarity index 97%
rename from src/vidmation/platforms/facebook.py
rename to src/aividio/platforms/facebook.py
index bd14adc..128bde5 100644
--- a/src/vidmation/platforms/facebook.py
+++ b/src/aividio/platforms/facebook.py
@@ -15,8 +15,8 @@
import ffmpeg
-from vidmation.platforms.base import Platform, PlatformType
-from vidmation.utils.ffmpeg import FFmpegError, get_duration, get_resolution
+from aividio.platforms.base import Platform, PlatformType
+from aividio.utils.ffmpeg import FFmpegError, get_duration, get_resolution
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/platforms/instagram.py b/src/aividio/platforms/instagram.py
similarity index 99%
rename from src/vidmation/platforms/instagram.py
rename to src/aividio/platforms/instagram.py
index cc3534d..8807414 100644
--- a/src/vidmation/platforms/instagram.py
+++ b/src/aividio/platforms/instagram.py
@@ -16,8 +16,8 @@
import ffmpeg
-from vidmation.platforms.base import Platform, PlatformType
-from vidmation.utils.ffmpeg import FFmpegError, get_duration, get_resolution
+from aividio.platforms.base import Platform, PlatformType
+from aividio.utils.ffmpeg import FFmpegError, get_duration, get_resolution
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/platforms/tiktok.py b/src/aividio/platforms/tiktok.py
similarity index 98%
rename from src/vidmation/platforms/tiktok.py
rename to src/aividio/platforms/tiktok.py
index e79f31d..edb576b 100644
--- a/src/vidmation/platforms/tiktok.py
+++ b/src/aividio/platforms/tiktok.py
@@ -15,8 +15,8 @@
import ffmpeg
-from vidmation.platforms.base import Platform, PlatformType
-from vidmation.utils.ffmpeg import FFmpegError, get_duration, get_resolution
+from aividio.platforms.base import Platform, PlatformType
+from aividio.utils.ffmpeg import FFmpegError, get_duration, get_resolution
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/platforms/twitter.py b/src/aividio/platforms/twitter.py
similarity index 96%
rename from src/vidmation/platforms/twitter.py
rename to src/aividio/platforms/twitter.py
index 5e680cb..3506ecc 100644
--- a/src/vidmation/platforms/twitter.py
+++ b/src/aividio/platforms/twitter.py
@@ -15,8 +15,8 @@
import ffmpeg
-from vidmation.platforms.base import Platform, PlatformType
-from vidmation.utils.ffmpeg import FFmpegError, get_duration, get_resolution
+from aividio.platforms.base import Platform, PlatformType
+from aividio.utils.ffmpeg import FFmpegError, get_duration, get_resolution
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/platforms/youtube.py b/src/aividio/platforms/youtube.py
similarity index 98%
rename from src/vidmation/platforms/youtube.py
rename to src/aividio/platforms/youtube.py
index 8043107..c877e81 100644
--- a/src/vidmation/platforms/youtube.py
+++ b/src/aividio/platforms/youtube.py
@@ -16,8 +16,8 @@
import ffmpeg
-from vidmation.platforms.base import Platform, PlatformType
-from vidmation.utils.ffmpeg import FFmpegError, get_duration, get_resolution
+from aividio.platforms.base import Platform, PlatformType
+from aividio.utils.ffmpeg import FFmpegError, get_duration, get_resolution
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/publishing/__init__.py b/src/aividio/publishing/__init__.py
similarity index 61%
rename from src/vidmation/publishing/__init__.py
rename to src/aividio/publishing/__init__.py
index 3378450..447ce37 100644
--- a/src/vidmation/publishing/__init__.py
+++ b/src/aividio/publishing/__init__.py
@@ -1,9 +1,9 @@
-"""Multi-platform publishing system for VIDMATION.
+"""Multi-platform publishing system for AIVIDIO.
Provides a unified interface to publish videos to YouTube, TikTok,
Instagram Reels, and future platforms via a single ``PublishManager``.
"""
-from vidmation.publishing.manager import PublishManager
+from aividio.publishing.manager import PublishManager
__all__ = ["PublishManager"]
diff --git a/src/vidmation/publishing/manager.py b/src/aividio/publishing/manager.py
similarity index 92%
rename from src/vidmation/publishing/manager.py
rename to src/aividio/publishing/manager.py
index 7b95a3b..b091160 100644
--- a/src/vidmation/publishing/manager.py
+++ b/src/aividio/publishing/manager.py
@@ -5,7 +5,7 @@
Usage::
- from vidmation.publishing.manager import PublishManager
+ from aividio.publishing.manager import PublishManager
pm = PublishManager()
results = pm.publish("video-uuid", platforms=["youtube", "tiktok"])
@@ -20,10 +20,10 @@
from pathlib import Path
from typing import Any
-from vidmation.db.engine import get_session
-from vidmation.models.video import Video, VideoStatus
+from aividio.db.engine import get_session
+from aividio.models.video import Video, VideoStatus
-logger = logging.getLogger("vidmation.publishing.manager")
+logger = logging.getLogger("aividio.publishing.manager")
class PublishManager:
@@ -112,12 +112,12 @@ def publish_to_youtube(self, video_id: str) -> str:
raise FileNotFoundError(f"Video file not found: {video_path}")
# Load channel's OAuth credentials
- from vidmation.models.channel import Channel
+ from aividio.models.channel import Channel
channel = session.get(Channel, video.channel_id)
if not channel or not channel.oauth_token_json:
raise ValueError(
f"Channel has no YouTube OAuth credentials configured. "
- f"Run 'vidmation auth youtube --channel {channel.name if channel else 'unknown'}' first."
+ f"Run 'aividio auth youtube --channel {channel.name if channel else 'unknown'}' first."
)
from google.oauth2.credentials import Credentials
@@ -125,7 +125,7 @@ def publish_to_youtube(self, video_id: str) -> str:
json.loads(channel.oauth_token_json)
)
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.services.youtube.uploader import YouTubeUploader
uploader = YouTubeUploader(credentials=creds)
yt_video_id = uploader.upload(
@@ -172,10 +172,10 @@ def publish_to_tiktok(self, video_id: str) -> str:
# Check for TikTok credentials
import os
- tiktok_access_token = os.getenv("VIDMATION_TIKTOK_ACCESS_TOKEN", "")
+ tiktok_access_token = os.getenv("AIVIDIO_TIKTOK_ACCESS_TOKEN", "")
if not tiktok_access_token:
raise NotImplementedError(
- "TikTok publishing requires VIDMATION_TIKTOK_ACCESS_TOKEN. "
+ "TikTok publishing requires AIVIDIO_TIKTOK_ACCESS_TOKEN. "
"Set up TikTok Content Publishing API credentials first."
)
@@ -253,23 +253,23 @@ def publish_to_instagram(self, video_id: str) -> str:
raise ValueError(f"Video '{video_id}' has no rendered file")
import os
- ig_access_token = os.getenv("VIDMATION_INSTAGRAM_ACCESS_TOKEN", "")
- ig_account_id = os.getenv("VIDMATION_INSTAGRAM_ACCOUNT_ID", "")
+ ig_access_token = os.getenv("AIVIDIO_INSTAGRAM_ACCESS_TOKEN", "")
+ ig_account_id = os.getenv("AIVIDIO_INSTAGRAM_ACCOUNT_ID", "")
if not ig_access_token or not ig_account_id:
raise NotImplementedError(
- "Instagram publishing requires VIDMATION_INSTAGRAM_ACCESS_TOKEN and "
- "VIDMATION_INSTAGRAM_ACCOUNT_ID. Set up Meta Graph API credentials first."
+ "Instagram publishing requires AIVIDIO_INSTAGRAM_ACCESS_TOKEN and "
+ "AIVIDIO_INSTAGRAM_ACCOUNT_ID. Set up Meta Graph API credentials first."
)
import httpx
# The video must be hosted at a public URL for Instagram's API.
# In production, this would upload to a CDN/S3 first.
- video_url = os.getenv("VIDMATION_PUBLIC_BASE_URL", "")
+ video_url = os.getenv("AIVIDIO_PUBLIC_BASE_URL", "")
if not video_url:
raise NotImplementedError(
- "Instagram publishing requires VIDMATION_PUBLIC_BASE_URL to serve "
+ "Instagram publishing requires AIVIDIO_PUBLIC_BASE_URL to serve "
"video files publicly. Configure a CDN or public file server."
)
@@ -373,7 +373,7 @@ def _schedule_publish(
schedule_at: datetime,
) -> dict[str, dict[str, str]]:
"""Delegate to AdvancedScheduler for deferred publishing."""
- from vidmation.scheduling.advanced import AdvancedScheduler
+ from aividio.scheduling.advanced import AdvancedScheduler
scheduler = AdvancedScheduler()
schedule = scheduler.schedule_video(
@@ -419,7 +419,7 @@ def _send_notification(
) -> None:
"""Send a publish-complete notification."""
try:
- from vidmation.notifications.manager import NotificationManager
+ from aividio.notifications.manager import NotificationManager
notifier = NotificationManager()
notifier.notify_publish_complete(
video_id=video_id,
diff --git a/src/aividio/queue/__init__.py b/src/aividio/queue/__init__.py
new file mode 100644
index 0000000..d19bc87
--- /dev/null
+++ b/src/aividio/queue/__init__.py
@@ -0,0 +1,6 @@
+"""Job queue system for AIVIDIO — worker, task helpers, and scheduler."""
+
+from aividio.queue.tasks import enqueue_video
+from aividio.queue.worker import JobWorker
+
+__all__ = ["JobWorker", "enqueue_video"]
diff --git a/src/vidmation/queue/scheduler.py b/src/aividio/queue/scheduler.py
similarity index 96%
rename from src/vidmation/queue/scheduler.py
rename to src/aividio/queue/scheduler.py
index 8e67306..b4d2754 100644
--- a/src/vidmation/queue/scheduler.py
+++ b/src/aividio/queue/scheduler.py
@@ -14,11 +14,11 @@
from datetime import datetime, timezone
from typing import Any
-from vidmation.config.profiles import ChannelProfile, load_profile
-from vidmation.config.settings import Settings, get_settings
-from vidmation.db.engine import get_session, init_db
-from vidmation.db.repos import ChannelRepo
-from vidmation.queue.tasks import enqueue_video
+from aividio.config.profiles import ChannelProfile, load_profile
+from aividio.config.settings import Settings, get_settings
+from aividio.db.engine import get_session, init_db
+from aividio.db.repos import ChannelRepo
+from aividio.queue.tasks import enqueue_video
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/queue/tasks.py b/src/aividio/queue/tasks.py
similarity index 92%
rename from src/vidmation/queue/tasks.py
rename to src/aividio/queue/tasks.py
index 3423399..de58733 100644
--- a/src/vidmation/queue/tasks.py
+++ b/src/aividio/queue/tasks.py
@@ -4,10 +4,10 @@
import logging
-from vidmation.db.engine import get_session, init_db
-from vidmation.db.repos import ChannelRepo, JobRepo, VideoRepo
-from vidmation.models.job import Job, JobStatus, JobType
-from vidmation.models.video import Video, VideoFormat, VideoStatus
+from aividio.db.engine import get_session, init_db
+from aividio.db.repos import ChannelRepo, JobRepo, VideoRepo
+from aividio.models.job import Job, JobStatus, JobType
+from aividio.models.video import Video, VideoFormat, VideoStatus
logger = logging.getLogger(__name__)
@@ -50,7 +50,7 @@ def enqueue_video(
if channel is None:
raise ValueError(
f"Channel '{channel_name}' not found. "
- f"Create it first with: vidmation channel add --name '{channel_name}'"
+ f"Create it first with: aividio channel add --name '{channel_name}'"
)
# Create video
diff --git a/src/vidmation/queue/worker.py b/src/aividio/queue/worker.py
similarity index 92%
rename from src/vidmation/queue/worker.py
rename to src/aividio/queue/worker.py
index 2d1618c..1dc6a21 100644
--- a/src/vidmation/queue/worker.py
+++ b/src/aividio/queue/worker.py
@@ -8,16 +8,16 @@
from datetime import datetime, timezone
from typing import TYPE_CHECKING
-from vidmation.config.profiles import ChannelProfile, load_profile
-from vidmation.config.settings import Settings, get_settings
-from vidmation.db.engine import get_session, init_db
-from vidmation.db.repos import ChannelRepo, JobRepo, VideoRepo
-from vidmation.models.job import Job, JobStatus, JobType
-from vidmation.models.video import VideoStatus
-from vidmation.pipeline.context import PipelineContext
-from vidmation.pipeline.orchestrator import PipelineOrchestrator
-from vidmation.pipeline.stages import STAGE_REGISTRY
-from vidmation.utils.files import get_work_dir
+from aividio.config.profiles import ChannelProfile, load_profile
+from aividio.config.settings import Settings, get_settings
+from aividio.db.engine import get_session, init_db
+from aividio.db.repos import ChannelRepo, JobRepo, VideoRepo
+from aividio.models.job import Job, JobStatus, JobType
+from aividio.models.video import VideoStatus
+from aividio.pipeline.context import PipelineContext
+from aividio.pipeline.orchestrator import PipelineOrchestrator
+from aividio.pipeline.stages import STAGE_REGISTRY
+from aividio.utils.files import get_work_dir
if TYPE_CHECKING:
pass
@@ -181,7 +181,7 @@ def _get_stages_for_job(self, job: Job) -> list[tuple[str, callable]]:
def _load_channel_profile(self, profile_path: str) -> ChannelProfile:
"""Load a channel profile, falling back to default."""
- from vidmation.config.profiles import get_default_profile
+ from aividio.config.profiles import get_default_profile
try:
return load_profile(profile_path)
diff --git a/src/vidmation/scheduling/__init__.py b/src/aividio/scheduling/__init__.py
similarity index 63%
rename from src/vidmation/scheduling/__init__.py
rename to src/aividio/scheduling/__init__.py
index 7aa90aa..0ca6f2e 100644
--- a/src/vidmation/scheduling/__init__.py
+++ b/src/aividio/scheduling/__init__.py
@@ -1,9 +1,9 @@
-"""Advanced scheduling system for VIDMATION.
+"""Advanced scheduling system for AIVIDIO.
Provides production-grade content scheduling with cron-based recurring
generation, one-time publish scheduling, and optimal timing suggestions.
"""
-from vidmation.scheduling.advanced import AdvancedScheduler
+from aividio.scheduling.advanced import AdvancedScheduler
__all__ = ["AdvancedScheduler"]
diff --git a/src/vidmation/scheduling/advanced.py b/src/aividio/scheduling/advanced.py
similarity index 96%
rename from src/vidmation/scheduling/advanced.py
rename to src/aividio/scheduling/advanced.py
index aea6a85..55190e4 100644
--- a/src/vidmation/scheduling/advanced.py
+++ b/src/aividio/scheduling/advanced.py
@@ -16,11 +16,11 @@
from sqlalchemy import select
-from vidmation.config.settings import Settings, get_settings
-from vidmation.db.engine import get_session, init_db
-from vidmation.models.schedule import Schedule, ScheduleStatus, ScheduleType
+from aividio.config.settings import Settings, get_settings
+from aividio.db.engine import get_session, init_db
+from aividio.models.schedule import Schedule, ScheduleStatus, ScheduleType
-logger = logging.getLogger("vidmation.scheduling.advanced")
+logger = logging.getLogger("aividio.scheduling.advanced")
# ---------------------------------------------------------------------------
@@ -163,7 +163,7 @@ def schedule_video(
try:
# Resolve channel_id from video if not provided
if channel_id is None:
- from vidmation.models.video import Video
+ from aividio.models.video import Video
video = session.get(Video, video_id)
if video:
channel_id = video.channel_id
@@ -216,7 +216,7 @@ def schedule_recurring(
session = get_session()
try:
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.repos import ChannelRepo
channel_repo = ChannelRepo(session)
channel = channel_repo.get_by_name(channel_name)
if not channel:
@@ -268,7 +268,7 @@ def get_schedule(
stmt = select(Schedule).order_by(Schedule.next_run_at.asc())
if channel_name:
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.repos import ChannelRepo
channel_repo = ChannelRepo(session)
channel = channel_repo.get_by_name(channel_name)
if channel:
@@ -358,12 +358,12 @@ def optimal_publish_times(self, channel_name: str) -> list[dict]:
try:
session = get_session()
try:
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.repos import ChannelRepo
channel_repo = ChannelRepo(session)
channel = channel_repo.get_by_name(channel_name)
if channel:
# Query past successful upload times and weight them
- from vidmation.models.video import Video, VideoStatus
+ from aividio.models.video import Video, VideoStatus
stmt = (
select(Video)
.where(
@@ -491,7 +491,7 @@ def _fire_one_time(
schedule.video_id, schedule.platforms,
)
try:
- from vidmation.publishing.manager import PublishManager
+ from aividio.publishing.manager import PublishManager
publisher = PublishManager()
publisher.publish(
video_id=schedule.video_id,
@@ -518,13 +518,13 @@ def _fire_recurring(
topic = self._pick_topic(session, schedule)
# Find the channel name for enqueue_video
- from vidmation.models.channel import Channel
+ from aividio.models.channel import Channel
channel = session.get(Channel, schedule.channel_id)
channel_name = channel.name if channel else "default"
# Enqueue video generation
try:
- from vidmation.queue.tasks import enqueue_video
+ from aividio.queue.tasks import enqueue_video
enqueue_video(
topic=topic,
channel_name=channel_name,
@@ -536,7 +536,7 @@ def _fire_recurring(
# Send notification
try:
- from vidmation.notifications.manager import NotificationManager
+ from aividio.notifications.manager import NotificationManager
notifier = NotificationManager()
notifier.notify_schedule_fired(
schedule_id=schedule.id,
@@ -588,11 +588,11 @@ def _pick_topic(self, session: Any, schedule: Schedule) -> str:
return "Trending topic"
# Default: "ai" — let the pipeline generate a topic
- from vidmation.models.channel import Channel
+ from aividio.models.channel import Channel
channel = session.get(Channel, schedule.channel_id)
if channel:
try:
- from vidmation.config.profiles import load_profile
+ from aividio.config.profiles import load_profile
profile = load_profile(channel.profile_path)
topics = profile.content.typical_topics
if topics:
diff --git a/src/vidmation/seo/__init__.py b/src/aividio/seo/__init__.py
similarity index 52%
rename from src/vidmation/seo/__init__.py
rename to src/aividio/seo/__init__.py
index 6c8d662..947ac4c 100644
--- a/src/vidmation/seo/__init__.py
+++ b/src/aividio/seo/__init__.py
@@ -1,6 +1,6 @@
"""SEO optimization tools for YouTube search and discovery."""
-from vidmation.seo.hashtags import HashtagGenerator
-from vidmation.seo.optimizer import SEOOptimizer
+from aividio.seo.hashtags import HashtagGenerator
+from aividio.seo.optimizer import SEOOptimizer
__all__ = ["SEOOptimizer", "HashtagGenerator"]
diff --git a/src/vidmation/seo/hashtags.py b/src/aividio/seo/hashtags.py
similarity index 97%
rename from src/vidmation/seo/hashtags.py
rename to src/aividio/seo/hashtags.py
index 9ff43b3..6e48865 100644
--- a/src/vidmation/seo/hashtags.py
+++ b/src/aividio/seo/hashtags.py
@@ -8,8 +8,8 @@
import anthropic
-from vidmation.config.settings import Settings, get_settings
-from vidmation.utils.retry import retry
+from aividio.config.settings import Settings, get_settings
+from aividio.utils.retry import retry
logger = logging.getLogger(__name__)
@@ -95,7 +95,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"anthropic_api_key is not configured. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
self._client = anthropic.Anthropic(api_key=api_key)
diff --git a/src/vidmation/seo/optimizer.py b/src/aividio/seo/optimizer.py
similarity index 97%
rename from src/vidmation/seo/optimizer.py
rename to src/aividio/seo/optimizer.py
index 94127ed..f94be11 100644
--- a/src/vidmation/seo/optimizer.py
+++ b/src/aividio/seo/optimizer.py
@@ -8,12 +8,12 @@
import anthropic
-from vidmation.config.settings import Settings, get_settings
-from vidmation.seo.hashtags import HashtagGenerator
-from vidmation.utils.retry import retry
+from aividio.config.settings import Settings, get_settings
+from aividio.seo.hashtags import HashtagGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.profiles import ChannelProfile
+ from aividio.config.profiles import ChannelProfile
logger = logging.getLogger(__name__)
@@ -145,7 +145,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"anthropic_api_key is not configured. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
self._client = anthropic.Anthropic(api_key=api_key)
self._hashtag_gen = HashtagGenerator(settings=self.settings)
diff --git a/src/aividio/services/__init__.py b/src/aividio/services/__init__.py
new file mode 100644
index 0000000..7a8c695
--- /dev/null
+++ b/src/aividio/services/__init__.py
@@ -0,0 +1,27 @@
+"""Service layer — API integrations for video production pipeline.
+
+Each sub-package exposes a factory function that returns the correct
+implementation based on application settings or an explicit provider name.
+"""
+
+from __future__ import annotations
+
+from aividio.services.base import BaseService
+from aividio.services.captions.whisper import WhisperCaptionGenerator
+from aividio.services.imagegen import create_image_generator
+from aividio.services.media import create_media_provider
+from aividio.services.scriptgen import create_script_generator
+from aividio.services.tts import create_tts_provider
+from aividio.services.youtube.auth import get_credentials
+from aividio.services.youtube.uploader import YouTubeUploader
+
+__all__ = [
+ "BaseService",
+ "WhisperCaptionGenerator",
+ "create_image_generator",
+ "create_media_provider",
+ "create_script_generator",
+ "create_tts_provider",
+ "get_credentials",
+ "YouTubeUploader",
+]
diff --git a/src/vidmation/services/assets/__init__.py b/src/aividio/services/assets/__init__.py
similarity index 64%
rename from src/vidmation/services/assets/__init__.py
rename to src/aividio/services/assets/__init__.py
index 807aa5c..82f9c85 100644
--- a/src/vidmation/services/assets/__init__.py
+++ b/src/aividio/services/assets/__init__.py
@@ -1,5 +1,5 @@
"""Custom asset management — upload, organise, and retrieve user assets."""
-from vidmation.services.assets.manager import AssetManager
+from aividio.services.assets.manager import AssetManager
__all__ = ["AssetManager"]
diff --git a/src/vidmation/services/assets/manager.py b/src/aividio/services/assets/manager.py
similarity index 97%
rename from src/vidmation/services/assets/manager.py
rename to src/aividio/services/assets/manager.py
index 1127ce3..35ef328 100644
--- a/src/vidmation/services/assets/manager.py
+++ b/src/aividio/services/assets/manager.py
@@ -1,7 +1,7 @@
"""AssetManager — upload, organise, query, and resolve custom media assets.
Assets are stored under ``{assets_dir}/uploads/{type}/{uuid}{ext}`` and tracked
-in the database via the :class:`~vidmation.models.asset.Asset` model.
+in the database via the :class:`~aividio.models.asset.Asset` model.
"""
from __future__ import annotations
@@ -15,9 +15,9 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
-from vidmation.config.settings import Settings, get_settings
-from vidmation.db.engine import get_session
-from vidmation.models.asset import Asset, AssetSource, AssetType
+from aividio.config.settings import Settings, get_settings
+from aividio.db.engine import get_session
+from aividio.models.asset import Asset, AssetSource, AssetType
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/services/avatars/__init__.py b/src/aividio/services/avatars/__init__.py
similarity index 71%
rename from src/vidmation/services/avatars/__init__.py
rename to src/aividio/services/avatars/__init__.py
index 1d766e8..c03995d 100644
--- a/src/vidmation/services/avatars/__init__.py
+++ b/src/aividio/services/avatars/__init__.py
@@ -4,11 +4,11 @@
from typing import TYPE_CHECKING
-from vidmation.config.settings import get_settings
-from vidmation.services.avatars.base import AvatarProvider
+from aividio.config.settings import get_settings
+from aividio.services.avatars.base import AvatarProvider
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
__all__ = ["AvatarProvider", "create_avatar_provider"]
@@ -26,12 +26,12 @@ def create_avatar_provider(
settings = settings or get_settings()
if provider == "replicate":
- from vidmation.services.avatars.replicate_avatar import ReplicateAvatarProvider
+ from aividio.services.avatars.replicate_avatar import ReplicateAvatarProvider
return ReplicateAvatarProvider(settings=settings)
if provider == "fal":
- from vidmation.services.avatars.fal_avatar import FalAvatarProvider
+ from aividio.services.avatars.fal_avatar import FalAvatarProvider
return FalAvatarProvider(settings=settings)
diff --git a/src/vidmation/services/avatars/base.py b/src/aividio/services/avatars/base.py
similarity index 95%
rename from src/vidmation/services/avatars/base.py
rename to src/aividio/services/avatars/base.py
index a5418df..d94ffb3 100644
--- a/src/vidmation/services/avatars/base.py
+++ b/src/aividio/services/avatars/base.py
@@ -5,8 +5,8 @@
from abc import abstractmethod
from pathlib import Path
-from vidmation.config.profiles import VoiceConfig
-from vidmation.services.base import BaseService
+from aividio.config.profiles import VoiceConfig
+from aividio.services.base import BaseService
class AvatarProvider(BaseService):
diff --git a/src/vidmation/services/avatars/fal_avatar.py b/src/aividio/services/avatars/fal_avatar.py
similarity index 95%
rename from src/vidmation/services/avatars/fal_avatar.py
rename to src/aividio/services/avatars/fal_avatar.py
index b441291..f5618de 100644
--- a/src/vidmation/services/avatars/fal_avatar.py
+++ b/src/aividio/services/avatars/fal_avatar.py
@@ -11,12 +11,12 @@
import fal_client
import httpx
-from vidmation.config.profiles import VoiceConfig
-from vidmation.services.avatars.base import AvatarProvider
-from vidmation.utils.retry import retry
+from aividio.config.profiles import VoiceConfig
+from aividio.services.avatars.base import AvatarProvider
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# Supported fal.ai avatar model endpoints.
@@ -76,7 +76,7 @@ def __init__(
if not fal_key:
raise ValueError(
"fal_key is not configured. "
- "Set VIDMATION_FAL_KEY in your environment."
+ "Set AIVIDIO_FAL_KEY in your environment."
)
os.environ["FAL_KEY"] = fal_key
@@ -189,13 +189,13 @@ def create_avatar(
def _generate_audio(self, text: str, voice_config: VoiceConfig) -> Path:
"""Generate TTS audio for the avatar."""
- from vidmation.services.tts import create_tts_provider
+ from aividio.services.tts import create_tts_provider
tts = create_tts_provider(
provider=voice_config.provider, settings=self.settings
)
- audio_dir = Path(tempfile.gettempdir()) / "vidmation" / "avatar_audio"
+ audio_dir = Path(tempfile.gettempdir()) / "aividio" / "avatar_audio"
audio_dir.mkdir(parents=True, exist_ok=True)
audio_path = audio_dir / f"avatar_tts_{uuid.uuid4().hex[:12]}.mp3"
diff --git a/src/vidmation/services/avatars/replicate_avatar.py b/src/aividio/services/avatars/replicate_avatar.py
similarity index 95%
rename from src/vidmation/services/avatars/replicate_avatar.py
rename to src/aividio/services/avatars/replicate_avatar.py
index 3b2b9ec..9cffe8a 100644
--- a/src/vidmation/services/avatars/replicate_avatar.py
+++ b/src/aividio/services/avatars/replicate_avatar.py
@@ -11,12 +11,12 @@
import replicate
from replicate.exceptions import ReplicateError
-from vidmation.config.profiles import VoiceConfig
-from vidmation.services.avatars.base import AvatarProvider
-from vidmation.utils.retry import retry
+from aividio.config.profiles import VoiceConfig
+from aividio.services.avatars.base import AvatarProvider
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# Replicate model identifiers for avatar generation.
@@ -75,7 +75,7 @@ def __init__(
if not api_token:
raise ValueError(
"replicate_api_token is not configured. "
- "Set VIDMATION_REPLICATE_API_TOKEN in your environment."
+ "Set AIVIDIO_REPLICATE_API_TOKEN in your environment."
)
self._client = replicate.Client(api_token=api_token)
self._model_key = model_key
@@ -187,13 +187,13 @@ def _generate_audio(self, text: str, voice_config: VoiceConfig) -> Path:
Uses the configured TTS provider from the voice config.
"""
- from vidmation.services.tts import create_tts_provider
+ from aividio.services.tts import create_tts_provider
tts = create_tts_provider(
provider=voice_config.provider, settings=self.settings
)
- audio_dir = Path(tempfile.gettempdir()) / "vidmation" / "avatar_audio"
+ audio_dir = Path(tempfile.gettempdir()) / "aividio" / "avatar_audio"
audio_dir.mkdir(parents=True, exist_ok=True)
audio_path = audio_dir / f"avatar_tts_{uuid.uuid4().hex[:12]}.mp3"
diff --git a/src/vidmation/services/base.py b/src/aividio/services/base.py
similarity index 83%
rename from src/vidmation/services/base.py
rename to src/aividio/services/base.py
index 8970306..9c5d032 100644
--- a/src/vidmation/services/base.py
+++ b/src/aividio/services/base.py
@@ -5,7 +5,7 @@
import logging
from abc import ABC
-from vidmation.config.settings import Settings, get_settings
+from aividio.config.settings import Settings, get_settings
class BaseService(ABC):
@@ -19,7 +19,7 @@ class BaseService(ABC):
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
self.logger = logging.getLogger(
- f"vidmation.services.{self.__class__.__name__}"
+ f"aividio.services.{self.__class__.__name__}"
)
def __repr__(self) -> str:
diff --git a/src/vidmation/services/blog2video/__init__.py b/src/aividio/services/blog2video/__init__.py
similarity index 66%
rename from src/vidmation/services/blog2video/__init__.py
rename to src/aividio/services/blog2video/__init__.py
index 2d60fde..f155ed3 100644
--- a/src/vidmation/services/blog2video/__init__.py
+++ b/src/aividio/services/blog2video/__init__.py
@@ -4,18 +4,18 @@
from typing import TYPE_CHECKING
-from vidmation.services.blog2video.converter import BlogToVideoConverter
-from vidmation.services.blog2video.scraper import BlogScraper
+from aividio.services.blog2video.converter import BlogToVideoConverter
+from aividio.services.blog2video.scraper import BlogScraper
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
__all__ = ["BlogScraper", "BlogToVideoConverter", "create_blog_converter"]
def create_blog_converter(settings: Settings | None = None) -> BlogToVideoConverter:
"""Factory: return a BlogToVideoConverter."""
- from vidmation.config.settings import get_settings
+ from aividio.config.settings import get_settings
settings = settings or get_settings()
return BlogToVideoConverter(settings=settings)
diff --git a/src/vidmation/services/blog2video/converter.py b/src/aividio/services/blog2video/converter.py
similarity index 95%
rename from src/vidmation/services/blog2video/converter.py
rename to src/aividio/services/blog2video/converter.py
index 934e87d..628f98f 100644
--- a/src/vidmation/services/blog2video/converter.py
+++ b/src/aividio/services/blog2video/converter.py
@@ -8,13 +8,13 @@
import openai
-from vidmation.services.base import BaseService
-from vidmation.services.blog2video.scraper import BlogScraper
-from vidmation.utils.retry import retry
+from aividio.services.base import BaseService
+from aividio.services.blog2video.scraper import BlogScraper
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.profiles import ChannelProfile
- from vidmation.config.settings import Settings
+ from aividio.config.profiles import ChannelProfile
+ from aividio.config.settings import Settings
logger = logging.getLogger(__name__)
@@ -118,7 +118,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"openai_api_key is not configured. "
- "Set VIDMATION_OPENAI_API_KEY in your environment."
+ "Set AIVIDIO_OPENAI_API_KEY in your environment."
)
self._client = openai.OpenAI(api_key=api_key)
self._scraper = BlogScraper()
@@ -178,7 +178,7 @@ def _generate_script(
channel_profile: ChannelProfile | None = None,
) -> dict[str, Any]:
"""Call GPT-4o to convert blog content into a video script."""
- from vidmation.config.profiles import get_default_profile
+ from aividio.config.profiles import get_default_profile
profile = channel_profile or get_default_profile()
diff --git a/src/vidmation/services/blog2video/scraper.py b/src/aividio/services/blog2video/scraper.py
similarity index 100%
rename from src/vidmation/services/blog2video/scraper.py
rename to src/aividio/services/blog2video/scraper.py
diff --git a/src/vidmation/services/captions/__init__.py b/src/aividio/services/captions/__init__.py
similarity index 69%
rename from src/vidmation/services/captions/__init__.py
rename to src/aividio/services/captions/__init__.py
index e3a5a86..ded52e6 100644
--- a/src/vidmation/services/captions/__init__.py
+++ b/src/aividio/services/captions/__init__.py
@@ -2,6 +2,6 @@
from __future__ import annotations
-from vidmation.services.captions.whisper import WhisperCaptionGenerator
+from aividio.services.captions.whisper import WhisperCaptionGenerator
__all__ = ["WhisperCaptionGenerator"]
diff --git a/src/vidmation/services/captions/whisper.py b/src/aividio/services/captions/whisper.py
similarity index 98%
rename from src/vidmation/services/captions/whisper.py
rename to src/aividio/services/captions/whisper.py
index 138dac3..6ac541b 100644
--- a/src/vidmation/services/captions/whisper.py
+++ b/src/aividio/services/captions/whisper.py
@@ -7,11 +7,11 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any
-from vidmation.services.base import BaseService
-from vidmation.utils.retry import retry
+from aividio.services.base import BaseService
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# Replicate model for Whisper (incredibly-fast-whisper with word timestamps).
_REPLICATE_MODEL = "vaibhavs10/incredibly-fast-whisper:3ab86df6c8f54c11309d4d1f930ac292bad43ace52d10c80d87eb258b3c9f79c"
@@ -38,7 +38,7 @@ def __init__(
if not api_token:
raise ValueError(
"replicate_api_token is required for Whisper via Replicate. "
- "Set VIDMATION_REPLICATE_API_TOKEN in your environment."
+ "Set AIVIDIO_REPLICATE_API_TOKEN in your environment."
)
import replicate
diff --git a/src/vidmation/services/imagegen/__init__.py b/src/aividio/services/imagegen/__init__.py
similarity index 72%
rename from src/vidmation/services/imagegen/__init__.py
rename to src/aividio/services/imagegen/__init__.py
index 5ddd421..ba99c89 100644
--- a/src/vidmation/services/imagegen/__init__.py
+++ b/src/aividio/services/imagegen/__init__.py
@@ -4,11 +4,11 @@
from typing import TYPE_CHECKING
-from vidmation.config.settings import get_settings
-from vidmation.services.imagegen.base import ImageGenerator
+from aividio.config.settings import get_settings
+from aividio.services.imagegen.base import ImageGenerator
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
__all__ = ["ImageGenerator", "create_image_generator"]
@@ -28,17 +28,17 @@ def create_image_generator(
provider = provider or settings.default_image_provider
if provider == "dalle":
- from vidmation.services.imagegen.dalle import DalleImageGenerator
+ from aividio.services.imagegen.dalle import DalleImageGenerator
return DalleImageGenerator(settings=settings)
if provider == "replicate":
- from vidmation.services.imagegen.replicate_gen import ReplicateImageGenerator
+ from aividio.services.imagegen.replicate_gen import ReplicateImageGenerator
return ReplicateImageGenerator(settings=settings)
if provider == "fal":
- from vidmation.services.imagegen.fal_gen import FalImageGenerator
+ from aividio.services.imagegen.fal_gen import FalImageGenerator
return FalImageGenerator(settings=settings)
diff --git a/src/vidmation/services/imagegen/base.py b/src/aividio/services/imagegen/base.py
similarity index 94%
rename from src/vidmation/services/imagegen/base.py
rename to src/aividio/services/imagegen/base.py
index 8229786..dac56dd 100644
--- a/src/vidmation/services/imagegen/base.py
+++ b/src/aividio/services/imagegen/base.py
@@ -5,7 +5,7 @@
from abc import abstractmethod
from pathlib import Path
-from vidmation.services.base import BaseService
+from aividio.services.base import BaseService
class ImageGenerator(BaseService):
diff --git a/src/vidmation/services/imagegen/dalle.py b/src/aividio/services/imagegen/dalle.py
similarity index 90%
rename from src/vidmation/services/imagegen/dalle.py
rename to src/aividio/services/imagegen/dalle.py
index b27dcb6..3848015 100644
--- a/src/vidmation/services/imagegen/dalle.py
+++ b/src/aividio/services/imagegen/dalle.py
@@ -10,11 +10,11 @@
import httpx
import openai
-from vidmation.services.imagegen.base import ImageGenerator
-from vidmation.utils.retry import retry
+from aividio.services.imagegen.base import ImageGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# DALL-E 3 supported sizes
_DALLE_SIZES = {
@@ -40,7 +40,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"openai_api_key is not configured. "
- "Set VIDMATION_OPENAI_API_KEY in your environment."
+ "Set AIVIDIO_OPENAI_API_KEY in your environment."
)
self._client = openai.OpenAI(api_key=api_key)
@@ -84,7 +84,7 @@ def generate(
# Determine output path.
if output_path is None:
- output_dir = Path(tempfile.gettempdir()) / "vidmation" / "imagegen"
+ output_dir = Path(tempfile.gettempdir()) / "aividio" / "imagegen"
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / f"dalle_{uuid.uuid4().hex[:12]}.png"
else:
diff --git a/src/vidmation/services/imagegen/fal_gen.py b/src/aividio/services/imagegen/fal_gen.py
similarity index 91%
rename from src/vidmation/services/imagegen/fal_gen.py
rename to src/aividio/services/imagegen/fal_gen.py
index a47c9f6..27317a0 100644
--- a/src/vidmation/services/imagegen/fal_gen.py
+++ b/src/aividio/services/imagegen/fal_gen.py
@@ -10,11 +10,11 @@
import fal_client
import httpx
-from vidmation.services.imagegen.base import ImageGenerator
-from vidmation.utils.retry import retry
+from aividio.services.imagegen.base import ImageGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# Default fal model endpoint — Flux Pro.
DEFAULT_MODEL = "fal-ai/flux/dev"
@@ -33,7 +33,7 @@ def __init__(
if not fal_key:
raise ValueError(
"fal_key is not configured. "
- "Set VIDMATION_FAL_KEY in your environment."
+ "Set AIVIDIO_FAL_KEY in your environment."
)
# fal_client reads FAL_KEY from the environment; set it explicitly.
import os
@@ -89,7 +89,7 @@ def generate(
# Determine output path.
if output_path is None:
- output_dir = Path(tempfile.gettempdir()) / "vidmation" / "imagegen"
+ output_dir = Path(tempfile.gettempdir()) / "aividio" / "imagegen"
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / f"fal_{uuid.uuid4().hex[:12]}.png"
else:
diff --git a/src/vidmation/services/imagegen/replicate_gen.py b/src/aividio/services/imagegen/replicate_gen.py
similarity index 91%
rename from src/vidmation/services/imagegen/replicate_gen.py
rename to src/aividio/services/imagegen/replicate_gen.py
index 013add9..58e036b 100644
--- a/src/vidmation/services/imagegen/replicate_gen.py
+++ b/src/aividio/services/imagegen/replicate_gen.py
@@ -11,11 +11,11 @@
import replicate
from replicate.exceptions import ReplicateError
-from vidmation.services.imagegen.base import ImageGenerator
-from vidmation.utils.retry import retry
+from aividio.services.imagegen.base import ImageGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# Default model — Flux Schnell (fast) on Replicate.
DEFAULT_MODEL = "black-forest-labs/flux-schnell"
@@ -34,7 +34,7 @@ def __init__(
if not api_token:
raise ValueError(
"replicate_api_token is not configured. "
- "Set VIDMATION_REPLICATE_API_TOKEN in your environment."
+ "Set AIVIDIO_REPLICATE_API_TOKEN in your environment."
)
self._client = replicate.Client(api_token=api_token)
self._model_id = model_id
@@ -86,7 +86,7 @@ def generate(
# Determine output path.
if output_path is None:
- output_dir = Path(tempfile.gettempdir()) / "vidmation" / "imagegen"
+ output_dir = Path(tempfile.gettempdir()) / "aividio" / "imagegen"
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / f"replicate_{uuid.uuid4().hex[:12]}.png"
else:
diff --git a/src/vidmation/services/media/__init__.py b/src/aividio/services/media/__init__.py
similarity index 73%
rename from src/vidmation/services/media/__init__.py
rename to src/aividio/services/media/__init__.py
index 54560b3..481f05d 100644
--- a/src/vidmation/services/media/__init__.py
+++ b/src/aividio/services/media/__init__.py
@@ -4,11 +4,11 @@
from typing import TYPE_CHECKING
-from vidmation.config.settings import get_settings
-from vidmation.services.media.base import MediaProvider
+from aividio.config.settings import get_settings
+from aividio.services.media.base import MediaProvider
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
__all__ = ["MediaProvider", "create_media_provider"]
@@ -26,12 +26,12 @@ def create_media_provider(
settings = settings or get_settings()
if provider == "pexels":
- from vidmation.services.media.pexels import PexelsMediaProvider
+ from aividio.services.media.pexels import PexelsMediaProvider
return PexelsMediaProvider(settings=settings)
if provider == "pixabay":
- from vidmation.services.media.pixabay import PixabayMediaProvider
+ from aividio.services.media.pixabay import PixabayMediaProvider
return PixabayMediaProvider(settings=settings)
diff --git a/src/vidmation/services/media/base.py b/src/aividio/services/media/base.py
similarity index 99%
rename from src/vidmation/services/media/base.py
rename to src/aividio/services/media/base.py
index a7424f8..8a66444 100644
--- a/src/vidmation/services/media/base.py
+++ b/src/aividio/services/media/base.py
@@ -7,7 +7,7 @@
from abc import abstractmethod
from pathlib import Path
-from vidmation.services.base import BaseService
+from aividio.services.base import BaseService
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/services/media/pexels.py b/src/aividio/services/media/pexels.py
similarity index 96%
rename from src/vidmation/services/media/pexels.py
rename to src/aividio/services/media/pexels.py
index 916f130..d15b06a 100644
--- a/src/vidmation/services/media/pexels.py
+++ b/src/aividio/services/media/pexels.py
@@ -7,11 +7,11 @@
import httpx
-from vidmation.services.media.base import MediaProvider
-from vidmation.utils.retry import retry
+from aividio.services.media.base import MediaProvider
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
_BASE_URL = "https://api.pexels.com"
@@ -25,7 +25,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"pexels_api_key is not configured. "
- "Set VIDMATION_PEXELS_API_KEY in your environment."
+ "Set AIVIDIO_PEXELS_API_KEY in your environment."
)
self._api_key = api_key
self._http = httpx.Client(
diff --git a/src/vidmation/services/media/pixabay.py b/src/aividio/services/media/pixabay.py
similarity index 95%
rename from src/vidmation/services/media/pixabay.py
rename to src/aividio/services/media/pixabay.py
index bed5f43..d5b7f5c 100644
--- a/src/vidmation/services/media/pixabay.py
+++ b/src/aividio/services/media/pixabay.py
@@ -7,11 +7,11 @@
import httpx
-from vidmation.services.media.base import MediaProvider
-from vidmation.utils.retry import retry
+from aividio.services.media.base import MediaProvider
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
_BASE_URL = "https://pixabay.com/api"
@@ -25,7 +25,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"pixabay_api_key is not configured. "
- "Set VIDMATION_PIXABAY_API_KEY in your environment."
+ "Set AIVIDIO_PIXABAY_API_KEY in your environment."
)
self._api_key = api_key
self._http = httpx.Client(
diff --git a/src/vidmation/services/models/__init__.py b/src/aividio/services/models/__init__.py
similarity index 68%
rename from src/vidmation/services/models/__init__.py
rename to src/aividio/services/models/__init__.py
index 376d76f..cc2e081 100644
--- a/src/vidmation/services/models/__init__.py
+++ b/src/aividio/services/models/__init__.py
@@ -2,6 +2,6 @@
from __future__ import annotations
-from vidmation.services.models.orchestrator import ModelOrchestrator
+from aividio.services.models.orchestrator import ModelOrchestrator
__all__ = ["ModelOrchestrator"]
diff --git a/src/vidmation/services/models/orchestrator.py b/src/aividio/services/models/orchestrator.py
similarity index 98%
rename from src/vidmation/services/models/orchestrator.py
rename to src/aividio/services/models/orchestrator.py
index c743629..8a87250 100644
--- a/src/vidmation/services/models/orchestrator.py
+++ b/src/aividio/services/models/orchestrator.py
@@ -13,12 +13,12 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any
-from vidmation.config.settings import Settings, get_settings
-from vidmation.services.videogen import create_video_generator
-from vidmation.services.videogen.base import VideoGenerator
+from aividio.config.settings import Settings, get_settings
+from aividio.services.videogen import create_video_generator
+from aividio.services.videogen.base import VideoGenerator
if TYPE_CHECKING:
- from vidmation.config.profiles import ChannelProfile
+ from aividio.config.profiles import ChannelProfile
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/services/music/__init__.py b/src/aividio/services/music/__init__.py
similarity index 78%
rename from src/vidmation/services/music/__init__.py
rename to src/aividio/services/music/__init__.py
index 5f4ea4a..9632273 100644
--- a/src/vidmation/services/music/__init__.py
+++ b/src/aividio/services/music/__init__.py
@@ -6,6 +6,6 @@
from __future__ import annotations
-from vidmation.services.music.selector import MusicSelector
+from aividio.services.music.selector import MusicSelector
__all__ = ["MusicSelector"]
diff --git a/src/vidmation/services/music/selector.py b/src/aividio/services/music/selector.py
similarity index 99%
rename from src/vidmation/services/music/selector.py
rename to src/aividio/services/music/selector.py
index 04e6097..38ae77c 100644
--- a/src/vidmation/services/music/selector.py
+++ b/src/aividio/services/music/selector.py
@@ -21,7 +21,7 @@
import httpx
if TYPE_CHECKING:
- from vidmation.config.profiles import ChannelProfile
+ from aividio.config.profiles import ChannelProfile
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/services/repurpose/__init__.py b/src/aividio/services/repurpose/__init__.py
similarity index 69%
rename from src/vidmation/services/repurpose/__init__.py
rename to src/aividio/services/repurpose/__init__.py
index 0acb2e2..19d9ac5 100644
--- a/src/vidmation/services/repurpose/__init__.py
+++ b/src/aividio/services/repurpose/__init__.py
@@ -4,11 +4,11 @@
from typing import TYPE_CHECKING
-from vidmation.config.settings import get_settings
+from aividio.config.settings import get_settings
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
- from vidmation.services.repurpose.generator import ContentRepurposer as ContentRepurposer
+ from aividio.config.settings import Settings
+ from aividio.services.repurpose.generator import ContentRepurposer as ContentRepurposer
__all__ = ["ContentRepurposer", "create_repurposer"]
@@ -22,7 +22,7 @@ def create_repurposer(
settings: Optional settings override. Falls back to the global
``Settings`` singleton when *None*.
"""
- from vidmation.services.repurpose.generator import ContentRepurposer
+ from aividio.services.repurpose.generator import ContentRepurposer
settings = settings or get_settings()
return ContentRepurposer(settings=settings)
diff --git a/src/vidmation/services/repurpose/generator.py b/src/aividio/services/repurpose/generator.py
similarity index 97%
rename from src/vidmation/services/repurpose/generator.py
rename to src/aividio/services/repurpose/generator.py
index 346f107..dafbfdb 100644
--- a/src/vidmation/services/repurpose/generator.py
+++ b/src/aividio/services/repurpose/generator.py
@@ -7,12 +7,12 @@
import openai
-from vidmation.config.profiles import ChannelProfile
-from vidmation.services.base import BaseService
-from vidmation.utils.retry import retry
+from aividio.config.profiles import ChannelProfile
+from aividio.services.base import BaseService
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# All supported platform keys.
ALL_PLATFORMS: list[str] = [
@@ -289,7 +289,7 @@ class ContentRepurposer(BaseService):
Usage::
- from vidmation.services.repurpose import create_repurposer
+ from aividio.services.repurpose import create_repurposer
repurposer = create_repurposer()
social_content = repurposer.generate(
@@ -308,7 +308,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"openai_api_key is not configured. "
- "Set VIDMATION_OPENAI_API_KEY in your environment."
+ "Set AIVIDIO_OPENAI_API_KEY in your environment."
)
self._client = openai.OpenAI(api_key=api_key)
diff --git a/src/vidmation/services/scriptgen/__init__.py b/src/aividio/services/scriptgen/__init__.py
similarity index 74%
rename from src/vidmation/services/scriptgen/__init__.py
rename to src/aividio/services/scriptgen/__init__.py
index 0395335..a01056e 100644
--- a/src/vidmation/services/scriptgen/__init__.py
+++ b/src/aividio/services/scriptgen/__init__.py
@@ -4,11 +4,11 @@
from typing import TYPE_CHECKING
-from vidmation.config.settings import get_settings
-from vidmation.services.scriptgen.base import ScriptGenerator
+from aividio.config.settings import get_settings
+from aividio.services.scriptgen.base import ScriptGenerator
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
__all__ = ["ScriptGenerator", "create_script_generator"]
@@ -28,12 +28,12 @@ def create_script_generator(
provider = provider or settings.default_llm_provider
if provider == "claude":
- from vidmation.services.scriptgen.claude import ClaudeScriptGenerator
+ from aividio.services.scriptgen.claude import ClaudeScriptGenerator
return ClaudeScriptGenerator(settings=settings)
if provider == "openai":
- from vidmation.services.scriptgen.openai_gen import OpenAIScriptGenerator
+ from aividio.services.scriptgen.openai_gen import OpenAIScriptGenerator
return OpenAIScriptGenerator(settings=settings)
diff --git a/src/vidmation/services/scriptgen/base.py b/src/aividio/services/scriptgen/base.py
similarity index 95%
rename from src/vidmation/services/scriptgen/base.py
rename to src/aividio/services/scriptgen/base.py
index c5254ef..4ca53ce 100644
--- a/src/vidmation/services/scriptgen/base.py
+++ b/src/aividio/services/scriptgen/base.py
@@ -5,8 +5,8 @@
from abc import abstractmethod
from typing import Any
-from vidmation.config.profiles import ChannelProfile
-from vidmation.services.base import BaseService
+from aividio.config.profiles import ChannelProfile
+from aividio.services.base import BaseService
# Canonical JSON schema that every generator must return.
SCRIPT_SCHEMA: dict[str, Any] = {
diff --git a/src/vidmation/services/scriptgen/claude.py b/src/aividio/services/scriptgen/claude.py
similarity index 92%
rename from src/vidmation/services/scriptgen/claude.py
rename to src/aividio/services/scriptgen/claude.py
index 6e5b6cf..5004c92 100644
--- a/src/vidmation/services/scriptgen/claude.py
+++ b/src/aividio/services/scriptgen/claude.py
@@ -7,12 +7,12 @@
import anthropic
-from vidmation.config.profiles import ChannelProfile
-from vidmation.services.scriptgen.base import SCRIPT_SCHEMA, ScriptGenerator
-from vidmation.utils.retry import retry
+from aividio.config.profiles import ChannelProfile
+from aividio.services.scriptgen.base import SCRIPT_SCHEMA, ScriptGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
_SYSTEM_PROMPT = """\
You are a professional YouTube scriptwriter specialising in faceless \
@@ -60,7 +60,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"anthropic_api_key is not configured. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
self._client = anthropic.Anthropic(api_key=api_key)
diff --git a/src/vidmation/services/scriptgen/openai_gen.py b/src/aividio/services/scriptgen/openai_gen.py
similarity index 92%
rename from src/vidmation/services/scriptgen/openai_gen.py
rename to src/aividio/services/scriptgen/openai_gen.py
index 0bddd99..ff1bdd3 100644
--- a/src/vidmation/services/scriptgen/openai_gen.py
+++ b/src/aividio/services/scriptgen/openai_gen.py
@@ -7,12 +7,12 @@
import openai
-from vidmation.config.profiles import ChannelProfile
-from vidmation.services.scriptgen.base import SCRIPT_SCHEMA, ScriptGenerator
-from vidmation.utils.retry import retry
+from aividio.config.profiles import ChannelProfile
+from aividio.services.scriptgen.base import SCRIPT_SCHEMA, ScriptGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
_SYSTEM_PROMPT = """\
You are a professional YouTube scriptwriter specialising in faceless \
@@ -60,7 +60,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"openai_api_key is not configured. "
- "Set VIDMATION_OPENAI_API_KEY in your environment."
+ "Set AIVIDIO_OPENAI_API_KEY in your environment."
)
self._client = openai.OpenAI(api_key=api_key)
diff --git a/src/vidmation/services/scriptgen/prompt_packs.py b/src/aividio/services/scriptgen/prompt_packs.py
similarity index 96%
rename from src/vidmation/services/scriptgen/prompt_packs.py
rename to src/aividio/services/scriptgen/prompt_packs.py
index 0a11654..18349f8 100644
--- a/src/vidmation/services/scriptgen/prompt_packs.py
+++ b/src/aividio/services/scriptgen/prompt_packs.py
@@ -8,12 +8,12 @@
import anthropic
-from vidmation.config.profiles import ChannelProfile
-from vidmation.services.base import BaseService
-from vidmation.utils.retry import retry
+from aividio.config.profiles import ChannelProfile
+from aividio.services.base import BaseService
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
logger = logging.getLogger(__name__)
@@ -105,7 +105,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"anthropic_api_key is not configured. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
self._client = anthropic.Anthropic(api_key=api_key)
diff --git a/src/vidmation/services/scriptgen/retention.py b/src/aividio/services/scriptgen/retention.py
similarity index 98%
rename from src/vidmation/services/scriptgen/retention.py
rename to src/aividio/services/scriptgen/retention.py
index 2c8bd40..5c97f3c 100644
--- a/src/vidmation/services/scriptgen/retention.py
+++ b/src/aividio/services/scriptgen/retention.py
@@ -9,12 +9,12 @@
import anthropic
-from vidmation.config.profiles import ChannelProfile
-from vidmation.services.base import BaseService
-from vidmation.utils.retry import retry
+from aividio.config.profiles import ChannelProfile
+from aividio.services.base import BaseService
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
logger = logging.getLogger(__name__)
@@ -185,7 +185,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"anthropic_api_key is not configured. "
- "Set VIDMATION_ANTHROPIC_API_KEY in your environment."
+ "Set AIVIDIO_ANTHROPIC_API_KEY in your environment."
)
self._client = anthropic.Anthropic(api_key=api_key)
diff --git a/src/vidmation/services/tts/__init__.py b/src/aividio/services/tts/__init__.py
similarity index 72%
rename from src/vidmation/services/tts/__init__.py
rename to src/aividio/services/tts/__init__.py
index 9f7ff83..0c38190 100644
--- a/src/vidmation/services/tts/__init__.py
+++ b/src/aividio/services/tts/__init__.py
@@ -4,11 +4,11 @@
from typing import TYPE_CHECKING
-from vidmation.config.settings import get_settings
-from vidmation.services.tts.base import TTSProvider
+from aividio.config.settings import get_settings
+from aividio.services.tts.base import TTSProvider
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
__all__ = ["TTSProvider", "create_tts_provider"]
@@ -29,22 +29,22 @@ def create_tts_provider(
provider = provider or settings.default_tts_provider
if provider == "elevenlabs":
- from vidmation.services.tts.elevenlabs import ElevenLabsTTS
+ from aividio.services.tts.elevenlabs import ElevenLabsTTS
return ElevenLabsTTS(settings=settings)
if provider == "openai":
- from vidmation.services.tts.openai_tts import OpenAITTS
+ from aividio.services.tts.openai_tts import OpenAITTS
return OpenAITTS(settings=settings)
if provider == "replicate":
- from vidmation.services.tts.replicate_tts import ReplicateTTS
+ from aividio.services.tts.replicate_tts import ReplicateTTS
return ReplicateTTS(settings=settings)
if provider == "fal":
- from vidmation.services.tts.fal_tts import FalTTS
+ from aividio.services.tts.fal_tts import FalTTS
return FalTTS(settings=settings)
diff --git a/src/vidmation/services/tts/base.py b/src/aividio/services/tts/base.py
similarity index 90%
rename from src/vidmation/services/tts/base.py
rename to src/aividio/services/tts/base.py
index e1fd1d3..a12c6c9 100644
--- a/src/vidmation/services/tts/base.py
+++ b/src/aividio/services/tts/base.py
@@ -5,8 +5,8 @@
from abc import abstractmethod
from pathlib import Path
-from vidmation.config.profiles import VoiceConfig
-from vidmation.services.base import BaseService
+from aividio.config.profiles import VoiceConfig
+from aividio.services.base import BaseService
class TTSProvider(BaseService):
diff --git a/src/vidmation/services/tts/elevenlabs.py b/src/aividio/services/tts/elevenlabs.py
similarity index 93%
rename from src/vidmation/services/tts/elevenlabs.py
rename to src/aividio/services/tts/elevenlabs.py
index a915384..7f3e5ed 100644
--- a/src/vidmation/services/tts/elevenlabs.py
+++ b/src/aividio/services/tts/elevenlabs.py
@@ -9,12 +9,12 @@
from elevenlabs import ElevenLabs
from elevenlabs.core import ApiError
-from vidmation.config.profiles import VoiceConfig
-from vidmation.services.tts.base import TTSProvider
-from vidmation.utils.retry import retry
+from aividio.config.profiles import VoiceConfig
+from aividio.services.tts.base import TTSProvider
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
def _get_mp3_duration(path: Path) -> float:
@@ -56,7 +56,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"elevenlabs_api_key is not configured. "
- "Set VIDMATION_ELEVENLABS_API_KEY in your environment."
+ "Set AIVIDIO_ELEVENLABS_API_KEY in your environment."
)
self._client = ElevenLabs(api_key=api_key)
diff --git a/src/vidmation/services/tts/fal_tts.py b/src/aividio/services/tts/fal_tts.py
similarity index 97%
rename from src/vidmation/services/tts/fal_tts.py
rename to src/aividio/services/tts/fal_tts.py
index 74d1086..dbafdae 100644
--- a/src/vidmation/services/tts/fal_tts.py
+++ b/src/aividio/services/tts/fal_tts.py
@@ -10,12 +10,12 @@
import fal_client
import httpx
-from vidmation.config.profiles import VoiceConfig
-from vidmation.services.tts.base import TTSProvider
-from vidmation.utils.retry import retry
+from aividio.config.profiles import VoiceConfig
+from aividio.services.tts.base import TTSProvider
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# Supported fal.ai TTS model endpoints.
@@ -58,7 +58,7 @@ def __init__(
if not fal_key:
raise ValueError(
"fal_key is not configured. "
- "Set VIDMATION_FAL_KEY in your environment."
+ "Set AIVIDIO_FAL_KEY in your environment."
)
# fal_client reads FAL_KEY from the environment.
os.environ["FAL_KEY"] = fal_key
diff --git a/src/vidmation/services/tts/openai_tts.py b/src/aividio/services/tts/openai_tts.py
similarity index 91%
rename from src/vidmation/services/tts/openai_tts.py
rename to src/aividio/services/tts/openai_tts.py
index 2901932..d48b64c 100644
--- a/src/vidmation/services/tts/openai_tts.py
+++ b/src/aividio/services/tts/openai_tts.py
@@ -7,12 +7,12 @@
import openai
-from vidmation.config.profiles import VoiceConfig
-from vidmation.services.tts.base import TTSProvider
-from vidmation.utils.retry import retry
+from aividio.config.profiles import VoiceConfig
+from aividio.services.tts.base import TTSProvider
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# OpenAI TTS available voices.
OPENAI_VOICES = ("alloy", "echo", "fable", "onyx", "nova", "shimmer")
@@ -36,7 +36,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"openai_api_key is not configured. "
- "Set VIDMATION_OPENAI_API_KEY in your environment."
+ "Set AIVIDIO_OPENAI_API_KEY in your environment."
)
self._client = openai.OpenAI(api_key=api_key)
diff --git a/src/vidmation/services/tts/replicate_tts.py b/src/aividio/services/tts/replicate_tts.py
similarity index 97%
rename from src/vidmation/services/tts/replicate_tts.py
rename to src/aividio/services/tts/replicate_tts.py
index 44c081f..4fffd8c 100644
--- a/src/vidmation/services/tts/replicate_tts.py
+++ b/src/aividio/services/tts/replicate_tts.py
@@ -10,12 +10,12 @@
import replicate
from replicate.exceptions import ReplicateError
-from vidmation.config.profiles import VoiceConfig
-from vidmation.services.tts.base import TTSProvider
-from vidmation.utils.retry import retry
+from aividio.config.profiles import VoiceConfig
+from aividio.services.tts.base import TTSProvider
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# Supported model catalogue with their Replicate identifiers.
@@ -63,7 +63,7 @@ def __init__(
if not api_token:
raise ValueError(
"replicate_api_token is not configured. "
- "Set VIDMATION_REPLICATE_API_TOKEN in your environment."
+ "Set AIVIDIO_REPLICATE_API_TOKEN in your environment."
)
self._client = replicate.Client(api_token=api_token)
self._model_key = model_key
diff --git a/src/vidmation/services/tts/voice_cloning.py b/src/aividio/services/tts/voice_cloning.py
similarity index 96%
rename from src/vidmation/services/tts/voice_cloning.py
rename to src/aividio/services/tts/voice_cloning.py
index edea273..571d75b 100644
--- a/src/vidmation/services/tts/voice_cloning.py
+++ b/src/aividio/services/tts/voice_cloning.py
@@ -10,11 +10,11 @@
import httpx
-from vidmation.config.settings import get_settings
-from vidmation.utils.retry import retry
+from aividio.config.settings import get_settings
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
logger = logging.getLogger(__name__)
@@ -35,7 +35,7 @@ class VoiceCloner:
def __init__(self, settings: Settings | None = None) -> None:
self.settings = settings or get_settings()
- self.logger = logging.getLogger(f"vidmation.services.{self.__class__.__name__}")
+ self.logger = logging.getLogger(f"aividio.services.{self.__class__.__name__}")
# ------------------------------------------------------------------
# ElevenLabs client (lazy init)
@@ -49,7 +49,7 @@ def _get_elevenlabs_client(self) -> Any:
if not api_key:
raise ValueError(
"elevenlabs_api_key is not configured. "
- "Set VIDMATION_ELEVENLABS_API_KEY in your environment."
+ "Set AIVIDIO_ELEVENLABS_API_KEY in your environment."
)
return ElevenLabs(api_key=api_key)
@@ -65,7 +65,7 @@ def _get_replicate_client(self) -> Any:
if not api_token:
raise ValueError(
"replicate_api_token is not configured. "
- "Set VIDMATION_REPLICATE_API_TOKEN in your environment."
+ "Set AIVIDIO_REPLICATE_API_TOKEN in your environment."
)
return replicate.Client(api_token=api_token)
@@ -311,7 +311,7 @@ def preview_voice(
text = text or self.DEFAULT_PREVIEW_TEXT
if output_dir is None:
- output_dir = Path(tempfile.gettempdir()) / "vidmation" / "voice_previews"
+ output_dir = Path(tempfile.gettempdir()) / "aividio" / "voice_previews"
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / f"preview_{voice_id[:20]}_{uuid.uuid4().hex[:8]}.mp3"
diff --git a/src/vidmation/services/videogen/__init__.py b/src/aividio/services/videogen/__init__.py
similarity index 78%
rename from src/vidmation/services/videogen/__init__.py
rename to src/aividio/services/videogen/__init__.py
index b52e42c..97d6674 100644
--- a/src/vidmation/services/videogen/__init__.py
+++ b/src/aividio/services/videogen/__init__.py
@@ -4,11 +4,11 @@
from typing import TYPE_CHECKING
-from vidmation.config.settings import get_settings
-from vidmation.services.videogen.base import VideoGenerator
+from aividio.config.settings import get_settings
+from aividio.services.videogen.base import VideoGenerator
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
__all__ = ["VideoGenerator", "create_video_generator"]
@@ -34,7 +34,7 @@ def create_video_generator(
provider = getattr(settings, "default_video_provider", "replicate")
if provider == "replicate":
- from vidmation.services.videogen.replicate_vid import ReplicateVideoGenerator
+ from aividio.services.videogen.replicate_vid import ReplicateVideoGenerator
kwargs: dict = {"settings": settings}
if model_id:
@@ -42,7 +42,7 @@ def create_video_generator(
return ReplicateVideoGenerator(**kwargs)
if provider == "fal":
- from vidmation.services.videogen.fal_vid import FalVideoGenerator
+ from aividio.services.videogen.fal_vid import FalVideoGenerator
kwargs = {"settings": settings}
if model_id:
@@ -50,7 +50,7 @@ def create_video_generator(
return FalVideoGenerator(**kwargs)
if provider == "local":
- from vidmation.services.videogen.local_gen import LocalVideoGenerator
+ from aividio.services.videogen.local_gen import LocalVideoGenerator
return LocalVideoGenerator(settings=settings)
diff --git a/src/vidmation/services/videogen/base.py b/src/aividio/services/videogen/base.py
similarity index 98%
rename from src/vidmation/services/videogen/base.py
rename to src/aividio/services/videogen/base.py
index 9ba88bf..993e4e4 100644
--- a/src/vidmation/services/videogen/base.py
+++ b/src/aividio/services/videogen/base.py
@@ -5,7 +5,7 @@
from abc import abstractmethod
from pathlib import Path
-from vidmation.services.base import BaseService
+from aividio.services.base import BaseService
class VideoGenerator(BaseService):
diff --git a/src/vidmation/services/videogen/fal_vid.py b/src/aividio/services/videogen/fal_vid.py
similarity index 97%
rename from src/vidmation/services/videogen/fal_vid.py
rename to src/aividio/services/videogen/fal_vid.py
index f163822..98c1b1e 100644
--- a/src/vidmation/services/videogen/fal_vid.py
+++ b/src/aividio/services/videogen/fal_vid.py
@@ -11,11 +11,11 @@
import fal_client
import httpx
-from vidmation.services.videogen.base import VideoGenerator
-from vidmation.utils.retry import retry
+from aividio.services.videogen.base import VideoGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# ---------------------------------------------------------------------------
# Supported models
@@ -81,7 +81,7 @@ def __init__(
if not fal_key:
raise ValueError(
"fal_key is not configured. "
- "Set VIDMATION_FAL_KEY in your environment."
+ "Set AIVIDIO_FAL_KEY in your environment."
)
# fal_client reads FAL_KEY from the environment.
import os
@@ -105,7 +105,7 @@ def _resolve_output_path(self, output_path: Path | None, prefix: str) -> Path:
output_path.parent.mkdir(parents=True, exist_ok=True)
return output_path
- output_dir = Path(tempfile.gettempdir()) / "vidmation" / "videogen"
+ output_dir = Path(tempfile.gettempdir()) / "aividio" / "videogen"
output_dir.mkdir(parents=True, exist_ok=True)
return output_dir / f"fal_{prefix}_{uuid.uuid4().hex[:12]}.mp4"
diff --git a/src/vidmation/services/videogen/local_gen.py b/src/aividio/services/videogen/local_gen.py
similarity index 98%
rename from src/vidmation/services/videogen/local_gen.py
rename to src/aividio/services/videogen/local_gen.py
index a1f261e..978a2bf 100644
--- a/src/vidmation/services/videogen/local_gen.py
+++ b/src/aividio/services/videogen/local_gen.py
@@ -18,10 +18,10 @@
import ffmpeg
-from vidmation.services.videogen.base import VideoGenerator
+from aividio.services.videogen.base import VideoGenerator
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# Aspect-ratio to pixel dimension mapping.
ASPECT_RATIO_MAP: dict[str, tuple[int, int]] = {
@@ -97,7 +97,7 @@ def _resolve_output_path(self, output_path: Path | None, prefix: str) -> Path:
if output_path is not None:
output_path.parent.mkdir(parents=True, exist_ok=True)
return output_path
- output_dir = Path(tempfile.gettempdir()) / "vidmation" / "videogen"
+ output_dir = Path(tempfile.gettempdir()) / "aividio" / "videogen"
output_dir.mkdir(parents=True, exist_ok=True)
return output_dir / f"local_{prefix}_{uuid.uuid4().hex[:12]}.mp4"
diff --git a/src/vidmation/services/videogen/replicate_vid.py b/src/aividio/services/videogen/replicate_vid.py
similarity index 97%
rename from src/vidmation/services/videogen/replicate_vid.py
rename to src/aividio/services/videogen/replicate_vid.py
index 3340f60..eabb93f 100644
--- a/src/vidmation/services/videogen/replicate_vid.py
+++ b/src/aividio/services/videogen/replicate_vid.py
@@ -13,11 +13,11 @@
import replicate
from replicate.exceptions import ReplicateError
-from vidmation.services.videogen.base import VideoGenerator
-from vidmation.utils.retry import retry
+from aividio.services.videogen.base import VideoGenerator
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# ---------------------------------------------------------------------------
# Supported models
@@ -82,7 +82,7 @@ def __init__(
if not api_token:
raise ValueError(
"replicate_api_token is not configured. "
- "Set VIDMATION_REPLICATE_API_TOKEN in your environment."
+ "Set AIVIDIO_REPLICATE_API_TOKEN in your environment."
)
self._client = replicate.Client(api_token=api_token)
self._model_id = model_id
@@ -103,7 +103,7 @@ def _resolve_output_path(self, output_path: Path | None, prefix: str) -> Path:
output_path.parent.mkdir(parents=True, exist_ok=True)
return output_path
- output_dir = Path(tempfile.gettempdir()) / "vidmation" / "videogen"
+ output_dir = Path(tempfile.gettempdir()) / "aividio" / "videogen"
output_dir.mkdir(parents=True, exist_ok=True)
return output_dir / f"replicate_{prefix}_{uuid.uuid4().hex[:12]}.mp4"
diff --git a/src/vidmation/services/youtube/__init__.py b/src/aividio/services/youtube/__init__.py
similarity index 65%
rename from src/vidmation/services/youtube/__init__.py
rename to src/aividio/services/youtube/__init__.py
index 7dce585..38f2f68 100644
--- a/src/vidmation/services/youtube/__init__.py
+++ b/src/aividio/services/youtube/__init__.py
@@ -2,15 +2,15 @@
from __future__ import annotations
-from vidmation.services.youtube.auth import (
+from aividio.services.youtube.auth import (
fetch_youtube_channel_info,
get_credentials,
get_credentials_for_channel,
store_credentials_for_channel,
)
-from vidmation.services.youtube.manager import YouTubeChannelManager
-from vidmation.services.youtube.metadata import YouTubeMetadataGenerator
-from vidmation.services.youtube.uploader import YouTubeUploader
+from aividio.services.youtube.manager import YouTubeChannelManager
+from aividio.services.youtube.metadata import YouTubeMetadataGenerator
+from aividio.services.youtube.uploader import YouTubeUploader
__all__ = [
"fetch_youtube_channel_info",
diff --git a/src/vidmation/services/youtube/auth.py b/src/aividio/services/youtube/auth.py
similarity index 97%
rename from src/vidmation/services/youtube/auth.py
rename to src/aividio/services/youtube/auth.py
index 65cd094..64109e6 100644
--- a/src/vidmation/services/youtube/auth.py
+++ b/src/aividio/services/youtube/auth.py
@@ -20,7 +20,7 @@
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
-logger = logging.getLogger("vidmation.services.youtube.auth")
+logger = logging.getLogger("aividio.services.youtube.auth")
# Scopes required for video upload + thumbnail management.
_SCOPES = [
@@ -141,8 +141,8 @@ def get_credentials_for_channel(
FileNotFoundError: If *client_secret_path* does not exist and a
new OAuth flow is required.
"""
- from vidmation.db.engine import get_session
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import ChannelRepo
scopes = scopes or _SCOPES
creds: Credentials | None = None
@@ -230,8 +230,8 @@ def store_credentials_for_channel(
session: An existing SQLAlchemy session. If ``None`` a new one
is created (and committed/closed internally).
"""
- from vidmation.db.engine import get_session as _get_session
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session as _get_session
+ from aividio.db.repos import ChannelRepo
own_session = session is None
if own_session:
diff --git a/src/vidmation/services/youtube/manager.py b/src/aividio/services/youtube/manager.py
similarity index 93%
rename from src/vidmation/services/youtube/manager.py
rename to src/aividio/services/youtube/manager.py
index 2589722..e59854e 100644
--- a/src/vidmation/services/youtube/manager.py
+++ b/src/aividio/services/youtube/manager.py
@@ -12,9 +12,9 @@
from pathlib import Path
from typing import Any
-from vidmation.config.settings import Settings, get_settings
+from aividio.config.settings import Settings, get_settings
-logger = logging.getLogger("vidmation.services.youtube.manager")
+logger = logging.getLogger("aividio.services.youtube.manager")
class YouTubeChannelManager:
@@ -58,9 +58,9 @@ def connect_channel(self, channel_name: str, client_secret_path: Path | None = N
ValueError: If the channel does not exist.
FileNotFoundError: If the client secret file is missing.
"""
- from vidmation.db.engine import get_session, init_db
- from vidmation.db.repos import ChannelRepo
- from vidmation.services.youtube.auth import (
+ from aividio.db.engine import get_session, init_db
+ from aividio.db.repos import ChannelRepo
+ from aividio.services.youtube.auth import (
fetch_youtube_channel_info,
get_credentials_for_channel,
)
@@ -114,8 +114,8 @@ def disconnect_channel(self, channel_name: str) -> None:
Raises:
ValueError: If the channel does not exist.
"""
- from vidmation.db.engine import get_session, init_db
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session, init_db
+ from aividio.db.repos import ChannelRepo
init_db()
session = get_session()
@@ -143,8 +143,8 @@ def list_connected_channels(self) -> list[dict[str, Any]]:
``youtube_channel_id``, ``youtube_channel_title``,
``connected_at``, and ``is_active``.
"""
- from vidmation.db.engine import get_session, init_db
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session, init_db
+ from aividio.db.repos import ChannelRepo
init_db()
session = get_session()
@@ -208,10 +208,10 @@ def publish_to_channel(
ValueError: If the channel is not found or not connected.
FileNotFoundError: If the video or client secret file is missing.
"""
- from vidmation.db.engine import get_session, init_db
- from vidmation.db.repos import ChannelRepo
- from vidmation.services.youtube.auth import get_credentials_for_channel
- from vidmation.services.youtube.uploader import YouTubeUploader
+ from aividio.db.engine import get_session, init_db
+ from aividio.db.repos import ChannelRepo
+ from aividio.services.youtube.auth import get_credentials_for_channel
+ from aividio.services.youtube.uploader import YouTubeUploader
video_path = Path(video_path)
thumb = Path(thumbnail_path) if thumbnail_path else None
@@ -226,7 +226,7 @@ def publish_to_channel(
if not channel.is_youtube_connected:
raise ValueError(
f"Channel '{channel_name}' is not connected to YouTube. "
- f"Run: vidmation youtube setup --channel {channel_name}"
+ f"Run: aividio youtube setup --channel {channel_name}"
)
creds = get_credentials_for_channel(
diff --git a/src/vidmation/services/youtube/metadata.py b/src/aividio/services/youtube/metadata.py
similarity index 98%
rename from src/vidmation/services/youtube/metadata.py
rename to src/aividio/services/youtube/metadata.py
index b8178e7..78ea5f8 100644
--- a/src/vidmation/services/youtube/metadata.py
+++ b/src/aividio/services/youtube/metadata.py
@@ -7,12 +7,12 @@
import openai
-from vidmation.config.profiles import ChannelProfile
-from vidmation.services.base import BaseService
-from vidmation.utils.retry import retry
+from aividio.config.profiles import ChannelProfile
+from aividio.services.base import BaseService
+from aividio.utils.retry import retry
if TYPE_CHECKING:
- from vidmation.config.settings import Settings
+ from aividio.config.settings import Settings
# YouTube API limits.
_MAX_TITLE_LENGTH = 100
@@ -216,7 +216,7 @@ def __init__(self, settings: Settings | None = None) -> None:
if not api_key:
raise ValueError(
"openai_api_key is not configured. "
- "Set VIDMATION_OPENAI_API_KEY in your environment."
+ "Set AIVIDIO_OPENAI_API_KEY in your environment."
)
self._client = openai.OpenAI(api_key=api_key)
diff --git a/src/vidmation/services/youtube/uploader.py b/src/aividio/services/youtube/uploader.py
similarity index 99%
rename from src/vidmation/services/youtube/uploader.py
rename to src/aividio/services/youtube/uploader.py
index 18c39a3..e2f65e9 100644
--- a/src/vidmation/services/youtube/uploader.py
+++ b/src/aividio/services/youtube/uploader.py
@@ -13,12 +13,12 @@
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaFileUpload
-from vidmation.utils.retry import retry
+from aividio.utils.retry import retry
if TYPE_CHECKING:
from google.oauth2.credentials import Credentials
-logger = logging.getLogger("vidmation.services.youtube.uploader")
+logger = logging.getLogger("aividio.services.youtube.uploader")
# Maximum number of resumable upload retries.
_MAX_RESUMABLE_RETRIES = 10
diff --git a/src/vidmation/styles/__init__.py b/src/aividio/styles/__init__.py
similarity index 73%
rename from src/vidmation/styles/__init__.py
rename to src/aividio/styles/__init__.py
index 0816be2..05f546f 100644
--- a/src/vidmation/styles/__init__.py
+++ b/src/aividio/styles/__init__.py
@@ -2,11 +2,11 @@
Public API::
- from vidmation.styles import (
+ from aividio.styles import (
# Low-level image/grade presets
VideoStyle, get_style, list_styles, build_image_prompt, get_ffmpeg_grade,
# High-level video templates (caption + transition + music + thumbnail)
VideoTemplate, VIDEO_TEMPLATES,
)
- from vidmation.styles.registry import get_template, list_templates, apply_template
+ from aividio.styles.registry import get_template, list_templates, apply_template
"""
diff --git a/src/vidmation/styles/coin_financials.py b/src/aividio/styles/coin_financials.py
similarity index 100%
rename from src/vidmation/styles/coin_financials.py
rename to src/aividio/styles/coin_financials.py
diff --git a/src/vidmation/styles/presets.py b/src/aividio/styles/presets.py
similarity index 100%
rename from src/vidmation/styles/presets.py
rename to src/aividio/styles/presets.py
diff --git a/src/vidmation/styles/registry.py b/src/aividio/styles/registry.py
similarity index 95%
rename from src/vidmation/styles/registry.py
rename to src/aividio/styles/registry.py
index e50aa75..ad5d026 100644
--- a/src/vidmation/styles/registry.py
+++ b/src/aividio/styles/registry.py
@@ -6,7 +6,7 @@
Usage::
- from vidmation.styles.registry import get_template, list_templates, apply_template
+ from aividio.styles.registry import get_template, list_templates, apply_template
tmpl = get_template("dark-cinematic")
profile = apply_template(profile, "tiktok-viral")
@@ -17,13 +17,13 @@
import copy
-from vidmation.config.profiles import (
+from aividio.config.profiles import (
ChannelProfile,
MusicConfig,
ThumbnailConfig,
VideoConfig,
)
-from vidmation.styles.presets import VIDEO_TEMPLATES, VideoTemplate
+from aividio.styles.presets import VIDEO_TEMPLATES, VideoTemplate
# ---------------------------------------------------------------------------
# Lookup helpers
diff --git a/src/aividio/utils/__init__.py b/src/aividio/utils/__init__.py
new file mode 100644
index 0000000..d047fa7
--- /dev/null
+++ b/src/aividio/utils/__init__.py
@@ -0,0 +1 @@
+"""Utility modules for AIVIDIO."""
diff --git a/src/vidmation/utils/ffmpeg.py b/src/aividio/utils/ffmpeg.py
similarity index 100%
rename from src/vidmation/utils/ffmpeg.py
rename to src/aividio/utils/ffmpeg.py
diff --git a/src/vidmation/utils/files.py b/src/aividio/utils/files.py
similarity index 89%
rename from src/vidmation/utils/files.py
rename to src/aividio/utils/files.py
index 1e000bf..0ba25e6 100644
--- a/src/vidmation/utils/files.py
+++ b/src/aividio/utils/files.py
@@ -5,7 +5,7 @@
import tempfile
from pathlib import Path
-from vidmation.config.settings import get_settings
+from aividio.config.settings import get_settings
def get_work_dir(video_id: str) -> Path:
@@ -24,7 +24,7 @@ def get_output_path(video_id: str, filename: str) -> Path:
return output_dir / f"{video_id}_{filename}"
-def create_temp_dir(prefix: str = "vidmation_") -> Path:
+def create_temp_dir(prefix: str = "aividio_") -> Path:
"""Create a temporary directory that persists until manually cleaned."""
return Path(tempfile.mkdtemp(prefix=prefix))
diff --git a/src/vidmation/utils/logging.py b/src/aividio/utils/logging.py
similarity index 93%
rename from src/vidmation/utils/logging.py
rename to src/aividio/utils/logging.py
index ed5e098..5c53602 100644
--- a/src/vidmation/utils/logging.py
+++ b/src/aividio/utils/logging.py
@@ -1,4 +1,4 @@
-"""Structured logging setup for VIDMATION."""
+"""Structured logging setup for AIVIDIO."""
from __future__ import annotations
diff --git a/src/vidmation/utils/retry.py b/src/aividio/utils/retry.py
similarity index 100%
rename from src/vidmation/utils/retry.py
rename to src/aividio/utils/retry.py
diff --git a/src/vidmation/video/__init__.py b/src/aividio/video/__init__.py
similarity index 83%
rename from src/vidmation/video/__init__.py
rename to src/aividio/video/__init__.py
index 75cca11..f2f5ab8 100644
--- a/src/vidmation/video/__init__.py
+++ b/src/aividio/video/__init__.py
@@ -1,4 +1,4 @@
-"""Video assembly engine for VIDMATION.
+"""Video assembly engine for AIVIDIO.
Submodules:
- assembler: Main :class:`VideoAssembler` orchestration class.
@@ -8,19 +8,19 @@
- formats: Video format specifications (landscape, portrait, short).
"""
-from vidmation.video.assembler import VideoAssembler
-from vidmation.video.audio_mixer import (
+from aividio.video.assembler import VideoAssembler
+from aividio.video.audio_mixer import (
get_audio_duration,
mix_voiceover_and_music,
normalize_audio,
)
-from vidmation.video.captions_render import (
+from aividio.video.captions_render import (
burn_captions,
generate_animated_ass,
generate_ass_file,
render_with_template,
)
-from vidmation.video.formats import (
+from aividio.video.formats import (
FORMAT_REGISTRY,
LANDSCAPE,
PORTRAIT,
@@ -28,7 +28,7 @@
FormatSpec,
get_format,
)
-from vidmation.video.transitions import (
+from aividio.video.transitions import (
TRANSITION_REGISTRY,
crossfade,
crossfade_with_offset,
diff --git a/src/vidmation/video/assembler.py b/src/aividio/video/assembler.py
similarity index 98%
rename from src/vidmation/video/assembler.py
rename to src/aividio/video/assembler.py
index 1f31d0a..3e108d5 100644
--- a/src/vidmation/video/assembler.py
+++ b/src/aividio/video/assembler.py
@@ -1,4 +1,4 @@
-"""Main video assembly engine for VIDMATION.
+"""Main video assembly engine for AIVIDIO.
Orchestrates clip fitting, transitions, audio mixing, caption burn-in,
and final encoding into a single cohesive video file.
@@ -17,11 +17,11 @@
import ffmpeg
-from vidmation.config.profiles import VideoConfig
-from vidmation.utils.ffmpeg import FFmpegError, get_duration
-from vidmation.video.audio_mixer import get_audio_duration, mix_voiceover_and_music
-from vidmation.video.captions_render import burn_captions, generate_ass_file
-from vidmation.video.formats import FormatSpec, get_format
+from aividio.config.profiles import VideoConfig
+from aividio.utils.ffmpeg import FFmpegError, get_duration
+from aividio.video.audio_mixer import get_audio_duration, mix_voiceover_and_music
+from aividio.video.captions_render import burn_captions, generate_ass_file
+from aividio.video.formats import FormatSpec, get_format
logger = logging.getLogger(__name__)
diff --git a/src/vidmation/video/audio_mixer.py b/src/aividio/video/audio_mixer.py
similarity index 97%
rename from src/vidmation/video/audio_mixer.py
rename to src/aividio/video/audio_mixer.py
index e444489..d48222f 100644
--- a/src/vidmation/video/audio_mixer.py
+++ b/src/aividio/video/audio_mixer.py
@@ -11,7 +11,7 @@
import ffmpeg
-from vidmation.utils.ffmpeg import FFmpegError, get_duration
+from aividio.utils.ffmpeg import FFmpegError, get_duration
logger = logging.getLogger(__name__)
@@ -19,7 +19,7 @@
def get_audio_duration(audio_path: Path) -> float:
"""Return the duration of an audio file in seconds.
- Convenience wrapper that delegates to :func:`vidmation.utils.ffmpeg.get_duration`.
+ Convenience wrapper that delegates to :func:`aividio.utils.ffmpeg.get_duration`.
Raises:
FileNotFoundError: If *audio_path* does not exist.
diff --git a/src/vidmation/video/captions_render.py b/src/aividio/video/captions_render.py
similarity index 97%
rename from src/vidmation/video/captions_render.py
rename to src/aividio/video/captions_render.py
index 3d35eeb..615348e 100644
--- a/src/vidmation/video/captions_render.py
+++ b/src/aividio/video/captions_render.py
@@ -7,7 +7,7 @@
And 35+ animated caption templates via :func:`render_with_template`::
- from vidmation.video.captions_render import render_with_template
+ from aividio.video.captions_render import render_with_template
output = render_with_template(
words=whisper_words,
@@ -23,7 +23,7 @@
from pathlib import Path
from textwrap import dedent
-from vidmation.utils.ffmpeg import FFmpegError
+from aividio.utils.ffmpeg import FFmpegError
logger = logging.getLogger(__name__)
@@ -141,7 +141,7 @@ def _build_ass_header(style: dict) -> str:
# ASS header template
return dedent(f"""\
[Script Info]
- Title: VIDMATION Captions
+ Title: AIVIDIO Captions
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
@@ -688,7 +688,7 @@ def render_with_template(
"""Generate and burn animated captions using a named template.
This is the high-level entry point that combines the new
- :class:`~vidmation.captions.animator.CaptionAnimator` with template
+ :class:`~aividio.captions.animator.CaptionAnimator` with template
selection and ffmpeg burn-in -- replacing the basic caption workflow
with the full Submagic-style animated version.
@@ -714,9 +714,9 @@ def render_with_template(
FileNotFoundError: If *video_path* does not exist.
FFmpegError: On ffmpeg failure.
"""
- from vidmation.captions.animator import CaptionAnimator
- from vidmation.captions.templates import get_template
- from vidmation.utils.ffmpeg import get_resolution
+ from aividio.captions.animator import CaptionAnimator
+ from aividio.captions.templates import get_template
+ from aividio.utils.ffmpeg import get_resolution
video_path = Path(video_path)
output_path = Path(output_path)
@@ -767,7 +767,7 @@ def generate_animated_ass(
) -> Path:
"""Generate an animated ASS file without burning into video.
- Convenience wrapper around :class:`~vidmation.captions.animator.CaptionAnimator`
+ Convenience wrapper around :class:`~aividio.captions.animator.CaptionAnimator`
for cases where you only need the subtitle file (e.g. for preview
or manual ffmpeg pipeline).
@@ -782,8 +782,8 @@ def generate_animated_ass(
Returns:
The *output_path* that was written to.
"""
- from vidmation.captions.animator import CaptionAnimator
- from vidmation.captions.templates import get_template
+ from aividio.captions.animator import CaptionAnimator
+ from aividio.captions.templates import get_template
template = get_template(template_name)
if template_overrides:
diff --git a/src/vidmation/video/formats.py b/src/aividio/video/formats.py
similarity index 97%
rename from src/vidmation/video/formats.py
rename to src/aividio/video/formats.py
index a40b83b..63ffe14 100644
--- a/src/vidmation/video/formats.py
+++ b/src/aividio/video/formats.py
@@ -1,4 +1,4 @@
-"""Video format specifications for VIDMATION output targets."""
+"""Video format specifications for AIVIDIO output targets."""
from __future__ import annotations
diff --git a/src/vidmation/video/transitions.py b/src/aividio/video/transitions.py
similarity index 100%
rename from src/vidmation/video/transitions.py
rename to src/aividio/video/transitions.py
diff --git a/src/aividio/web/__init__.py b/src/aividio/web/__init__.py
new file mode 100644
index 0000000..3535785
--- /dev/null
+++ b/src/aividio/web/__init__.py
@@ -0,0 +1 @@
+"""AIVIDIO web dashboard."""
diff --git a/src/vidmation/web/app.py b/src/aividio/web/app.py
similarity index 87%
rename from src/vidmation/web/app.py
rename to src/aividio/web/app.py
index 95a9dd7..caae9c6 100644
--- a/src/vidmation/web/app.py
+++ b/src/aividio/web/app.py
@@ -1,4 +1,4 @@
-"""FastAPI application factory for the VIDMATION web dashboard."""
+"""FastAPI application factory for the AIVIDIO web dashboard."""
from __future__ import annotations
@@ -9,10 +9,10 @@
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
-from vidmation.api.v1.router import router as api_v1_router
-from vidmation.auth.routes import router as auth_router
-from vidmation.db.engine import init_db
-from vidmation.web.templating import get_templates # noqa: F401 — re-exported for back-compat
+from aividio.api.v1.router import router as api_v1_router
+from aividio.auth.routes import router as auth_router
+from aividio.db.engine import init_db
+from aividio.web.templating import get_templates # noqa: F401 — re-exported for back-compat
STATIC_DIR = Path(__file__).parent / "static"
@@ -22,7 +22,7 @@ def create_app() -> FastAPI:
# Import route modules here (after templating is available) to avoid
# the circular-import caused by routes importing get_templates from this
# module before it is fully initialised.
- from vidmation.web.routes import ( # noqa: PLC0415
+ from aividio.web.routes import ( # noqa: PLC0415
analytics,
api,
channels,
diff --git a/src/vidmation/web/routes/__init__.py b/src/aividio/web/routes/__init__.py
similarity index 100%
rename from src/vidmation/web/routes/__init__.py
rename to src/aividio/web/routes/__init__.py
diff --git a/src/vidmation/web/routes/analytics.py b/src/aividio/web/routes/analytics.py
similarity index 95%
rename from src/vidmation/web/routes/analytics.py
rename to src/aividio/web/routes/analytics.py
index 096c3c7..a4591d3 100644
--- a/src/vidmation/web/routes/analytics.py
+++ b/src/aividio/web/routes/analytics.py
@@ -9,9 +9,9 @@
from fastapi import APIRouter, Query, Request
from fastapi.responses import HTMLResponse, StreamingResponse
-from vidmation.analytics.reports import ReportGenerator
-from vidmation.analytics.tracker import get_tracker
-from vidmation.web.templating import get_templates
+from aividio.analytics.reports import ReportGenerator
+from aividio.analytics.tracker import get_tracker
+from aividio.web.templating import get_templates
router = APIRouter()
@@ -116,8 +116,8 @@ async def analytics_performance(
efficiency = reports.efficiency_report()
# Get channels for filter dropdown
- from vidmation.db.engine import get_session
- from vidmation.db.repos import ChannelRepo
+ from aividio.db.engine import get_session
+ from aividio.db.repos import ChannelRepo
session = get_session()
try:
@@ -214,6 +214,6 @@ async def api_export_costs_csv(
iter([output.getvalue()]),
media_type="text/csv",
headers={
- "Content-Disposition": f"attachment; filename=vidmation_costs_{today_str}.csv"
+ "Content-Disposition": f"attachment; filename=aividio_costs_{today_str}.csv"
},
)
diff --git a/src/vidmation/web/routes/api.py b/src/aividio/web/routes/api.py
similarity index 94%
rename from src/vidmation/web/routes/api.py
rename to src/aividio/web/routes/api.py
index 1fabc57..b6b9ca1 100644
--- a/src/vidmation/web/routes/api.py
+++ b/src/aividio/web/routes/api.py
@@ -6,9 +6,9 @@
from fastapi import APIRouter
-from vidmation.db.engine import get_session
-from vidmation.db.repos import JobRepo
-from vidmation.models.job import JobStatus
+from aividio.db.engine import get_session
+from aividio.db.repos import JobRepo
+from aividio.models.job import JobStatus
router = APIRouter()
diff --git a/src/vidmation/web/routes/channels.py b/src/aividio/web/routes/channels.py
similarity index 93%
rename from src/vidmation/web/routes/channels.py
rename to src/aividio/web/routes/channels.py
index 259d185..b99c5fc 100644
--- a/src/vidmation/web/routes/channels.py
+++ b/src/aividio/web/routes/channels.py
@@ -5,9 +5,9 @@
from fastapi import APIRouter, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse
-from vidmation.db.engine import get_session
-from vidmation.db.repos import ChannelRepo
-from vidmation.web.templating import get_templates
+from aividio.db.engine import get_session
+from aividio.db.repos import ChannelRepo
+from aividio.web.templating import get_templates
router = APIRouter()
diff --git a/src/vidmation/web/routes/content.py b/src/aividio/web/routes/content.py
similarity index 96%
rename from src/vidmation/web/routes/content.py
rename to src/aividio/web/routes/content.py
index e80884b..19a33e2 100644
--- a/src/vidmation/web/routes/content.py
+++ b/src/aividio/web/routes/content.py
@@ -7,14 +7,14 @@
from fastapi import APIRouter, Form, Query, Request
from fastapi.responses import HTMLResponse, RedirectResponse, Response
-from vidmation.config.settings import get_settings
-from vidmation.content.calendar import ContentCalendar
-from vidmation.content.planner import ContentPlanner
-from vidmation.content.series import SeriesManager
-from vidmation.db.engine import get_session
-from vidmation.db.repos import ChannelRepo
-from vidmation.queue.tasks import enqueue_video
-from vidmation.web.templating import get_templates
+from aividio.config.settings import get_settings
+from aividio.content.calendar import ContentCalendar
+from aividio.content.planner import ContentPlanner
+from aividio.content.series import SeriesManager
+from aividio.db.engine import get_session
+from aividio.db.repos import ChannelRepo
+from aividio.queue.tasks import enqueue_video
+from aividio.web.templating import get_templates
router = APIRouter()
@@ -233,7 +233,7 @@ async def content_export_ical(calendar_id: str):
content=ical,
media_type="text/calendar",
headers={
- "Content-Disposition": f"attachment; filename=vidmation-calendar-{calendar_id[:8]}.ics"
+ "Content-Disposition": f"attachment; filename=aividio-calendar-{calendar_id[:8]}.ics"
},
)
diff --git a/src/vidmation/web/routes/dashboard.py b/src/aividio/web/routes/dashboard.py
similarity index 85%
rename from src/vidmation/web/routes/dashboard.py
rename to src/aividio/web/routes/dashboard.py
index cc9aa54..b2e68ce 100644
--- a/src/vidmation/web/routes/dashboard.py
+++ b/src/aividio/web/routes/dashboard.py
@@ -5,11 +5,11 @@
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
-from vidmation.db.engine import get_session
-from vidmation.db.repos import ChannelRepo, JobRepo, VideoRepo
-from vidmation.models.job import JobStatus
-from vidmation.models.video import VideoStatus
-from vidmation.web.templating import get_templates
+from aividio.db.engine import get_session
+from aividio.db.repos import ChannelRepo, JobRepo, VideoRepo
+from aividio.models.job import JobStatus
+from aividio.models.video import VideoStatus
+from aividio.web.templating import get_templates
router = APIRouter()
diff --git a/src/vidmation/web/routes/jobs.py b/src/aividio/web/routes/jobs.py
similarity index 88%
rename from src/vidmation/web/routes/jobs.py
rename to src/aividio/web/routes/jobs.py
index 3b29a55..a138d7e 100644
--- a/src/vidmation/web/routes/jobs.py
+++ b/src/aividio/web/routes/jobs.py
@@ -5,10 +5,10 @@
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
-from vidmation.db.engine import get_session
-from vidmation.db.repos import JobRepo
-from vidmation.models.job import JobStatus
-from vidmation.web.templating import get_templates
+from aividio.db.engine import get_session
+from aividio.db.repos import JobRepo
+from aividio.models.job import JobStatus
+from aividio.web.templating import get_templates
router = APIRouter()
diff --git a/src/vidmation/web/routes/notifications.py b/src/aividio/web/routes/notifications.py
similarity index 96%
rename from src/vidmation/web/routes/notifications.py
rename to src/aividio/web/routes/notifications.py
index 9e66921..65b6b08 100644
--- a/src/vidmation/web/routes/notifications.py
+++ b/src/aividio/web/routes/notifications.py
@@ -5,8 +5,8 @@
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, JSONResponse
-from vidmation.notifications.manager import NotificationManager
-from vidmation.web.templating import get_templates
+from aividio.notifications.manager import NotificationManager
+from aividio.web.templating import get_templates
router = APIRouter()
diff --git a/src/vidmation/web/routes/schedule.py b/src/aividio/web/routes/schedule.py
similarity index 96%
rename from src/vidmation/web/routes/schedule.py
rename to src/aividio/web/routes/schedule.py
index 5d8a416..b2b751e 100644
--- a/src/vidmation/web/routes/schedule.py
+++ b/src/aividio/web/routes/schedule.py
@@ -7,11 +7,11 @@
from fastapi import APIRouter, Form, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
-from vidmation.db.engine import get_session
-from vidmation.db.repos import ChannelRepo, VideoRepo
-from vidmation.models.video import VideoStatus
-from vidmation.scheduling.advanced import AdvancedScheduler
-from vidmation.web.templating import get_templates
+from aividio.db.engine import get_session
+from aividio.db.repos import ChannelRepo, VideoRepo
+from aividio.models.video import VideoStatus
+from aividio.scheduling.advanced import AdvancedScheduler
+from aividio.web.templating import get_templates
router = APIRouter()
diff --git a/src/vidmation/web/routes/videos.py b/src/aividio/web/routes/videos.py
similarity index 91%
rename from src/vidmation/web/routes/videos.py
rename to src/aividio/web/routes/videos.py
index 721e68a..3b2794b 100644
--- a/src/vidmation/web/routes/videos.py
+++ b/src/aividio/web/routes/videos.py
@@ -5,11 +5,11 @@
from fastapi import APIRouter, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse
-from vidmation.db.engine import get_session
-from vidmation.db.repos import ChannelRepo, VideoRepo
-from vidmation.models.video import VideoFormat, VideoStatus
-from vidmation.queue.tasks import enqueue_video
-from vidmation.web.templating import get_templates
+from aividio.db.engine import get_session
+from aividio.db.repos import ChannelRepo, VideoRepo
+from aividio.models.video import VideoFormat, VideoStatus
+from aividio.queue.tasks import enqueue_video
+from aividio.web.templating import get_templates
router = APIRouter()
diff --git a/src/vidmation/web/routes/voices.py b/src/aividio/web/routes/voices.py
similarity index 96%
rename from src/vidmation/web/routes/voices.py
rename to src/aividio/web/routes/voices.py
index 3b231f0..6bb006e 100644
--- a/src/vidmation/web/routes/voices.py
+++ b/src/aividio/web/routes/voices.py
@@ -9,11 +9,11 @@
from fastapi import APIRouter, File, Form, Request, UploadFile
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse
-from vidmation.config.settings import get_settings
-from vidmation.db.engine import get_session
-from vidmation.models.voice import Voice
-from vidmation.services.tts.voice_cloning import VoiceCloner
-from vidmation.web.templating import get_templates
+from aividio.config.settings import get_settings
+from aividio.db.engine import get_session
+from aividio.models.voice import Voice
+from aividio.services.tts.voice_cloning import VoiceCloner
+from aividio.web.templating import get_templates
router = APIRouter()
diff --git a/src/vidmation/web/static/css/custom.css b/src/aividio/web/static/css/custom.css
similarity index 99%
rename from src/vidmation/web/static/css/custom.css
rename to src/aividio/web/static/css/custom.css
index ae478ad..ffc432a 100644
--- a/src/vidmation/web/static/css/custom.css
+++ b/src/aividio/web/static/css/custom.css
@@ -1,5 +1,5 @@
/* ============================================================
- VIDMATION — OpenAI-Inspired Design System
+ AIVIDIO — OpenAI-Inspired Design System
============================================================
Near-black surfaces, accent green (#10a37f), Inter typography.
Minimal decoration. Content-forward. Every pixel intentional.
diff --git a/src/vidmation/web/templates/analytics/costs.html b/src/aividio/web/templates/analytics/costs.html
similarity index 99%
rename from src/vidmation/web/templates/analytics/costs.html
rename to src/aividio/web/templates/analytics/costs.html
index 27b9358..a78a6f2 100644
--- a/src/vidmation/web/templates/analytics/costs.html
+++ b/src/aividio/web/templates/analytics/costs.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Cost Details - VIDMATION{% endblock %}
+{% block title %}Cost Details - AIVIDIO{% endblock %}
{% block nav_analytics %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/analytics/dashboard.html b/src/aividio/web/templates/analytics/dashboard.html
similarity index 99%
rename from src/vidmation/web/templates/analytics/dashboard.html
rename to src/aividio/web/templates/analytics/dashboard.html
index 9eb3ab3..36f23d8 100644
--- a/src/vidmation/web/templates/analytics/dashboard.html
+++ b/src/aividio/web/templates/analytics/dashboard.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Analytics - VIDMATION{% endblock %}
+{% block title %}Analytics - AIVIDIO{% endblock %}
{% block nav_analytics %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/analytics/performance.html b/src/aividio/web/templates/analytics/performance.html
similarity index 99%
rename from src/vidmation/web/templates/analytics/performance.html
rename to src/aividio/web/templates/analytics/performance.html
index 3b0268b..e23a741 100644
--- a/src/vidmation/web/templates/analytics/performance.html
+++ b/src/aividio/web/templates/analytics/performance.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Performance - VIDMATION{% endblock %}
+{% block title %}Performance - AIVIDIO{% endblock %}
{% block nav_analytics %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/base.html b/src/aividio/web/templates/base.html
similarity index 99%
rename from src/vidmation/web/templates/base.html
rename to src/aividio/web/templates/base.html
index 7065b11..120085a 100644
--- a/src/vidmation/web/templates/base.html
+++ b/src/aividio/web/templates/base.html
@@ -3,7 +3,7 @@
-
{% block title %}VIDMATION{% endblock %}
+
{% block title %}AIVIDIO{% endblock %}
{# Tailwind CDN #}
@@ -58,7 +58,7 @@
- VIDMATION
+ AIVIDIO
@@ -282,7 +282,7 @@
class="absolute right-0 mt-2 w-52 rounded-xl bg-[#1a1a1a] border border-white/[0.08] overflow-hidden shadow-xl shadow-black/40">
Admin User
-
admin@vidmation.io
+
admin@aividio.io
diff --git a/src/vidmation/web/templates/channels/detail.html b/src/aividio/web/templates/channels/detail.html
similarity index 98%
rename from src/vidmation/web/templates/channels/detail.html
rename to src/aividio/web/templates/channels/detail.html
index 68a9b5b..e9cb6bd 100644
--- a/src/vidmation/web/templates/channels/detail.html
+++ b/src/aividio/web/templates/channels/detail.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}{{ channel.name }} - VIDMATION{% endblock %}
+{% block title %}{{ channel.name }} - AIVIDIO{% endblock %}
{% block nav_channels %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/channels/list.html b/src/aividio/web/templates/channels/list.html
similarity index 98%
rename from src/vidmation/web/templates/channels/list.html
rename to src/aividio/web/templates/channels/list.html
index cb2c29d..4235b6a 100644
--- a/src/vidmation/web/templates/channels/list.html
+++ b/src/aividio/web/templates/channels/list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Channels - VIDMATION{% endblock %}
+{% block title %}Channels - AIVIDIO{% endblock %}
{% block nav_channels %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/channels/new.html b/src/aividio/web/templates/channels/new.html
similarity index 97%
rename from src/vidmation/web/templates/channels/new.html
rename to src/aividio/web/templates/channels/new.html
index a3dd6a3..ac212d6 100644
--- a/src/vidmation/web/templates/channels/new.html
+++ b/src/aividio/web/templates/channels/new.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}New Channel - VIDMATION{% endblock %}
+{% block title %}New Channel - AIVIDIO{% endblock %}
{% block nav_channels %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/components/activity_feed.html b/src/aividio/web/templates/components/activity_feed.html
similarity index 100%
rename from src/vidmation/web/templates/components/activity_feed.html
rename to src/aividio/web/templates/components/activity_feed.html
diff --git a/src/vidmation/web/templates/components/command_palette.html b/src/aividio/web/templates/components/command_palette.html
similarity index 100%
rename from src/vidmation/web/templates/components/command_palette.html
rename to src/aividio/web/templates/components/command_palette.html
diff --git a/src/vidmation/web/templates/components/toast.html b/src/aividio/web/templates/components/toast.html
similarity index 100%
rename from src/vidmation/web/templates/components/toast.html
rename to src/aividio/web/templates/components/toast.html
diff --git a/src/vidmation/web/templates/content/calendar.html b/src/aividio/web/templates/content/calendar.html
similarity index 99%
rename from src/vidmation/web/templates/content/calendar.html
rename to src/aividio/web/templates/content/calendar.html
index 1de9042..9bd6e83 100644
--- a/src/vidmation/web/templates/content/calendar.html
+++ b/src/aividio/web/templates/content/calendar.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Content Calendar - VIDMATION{% endblock %}
+{% block title %}Content Calendar - AIVIDIO{% endblock %}
{% block nav_content %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/content/series.html b/src/aividio/web/templates/content/series.html
similarity index 99%
rename from src/vidmation/web/templates/content/series.html
rename to src/aividio/web/templates/content/series.html
index 163282d..e43ab2a 100644
--- a/src/vidmation/web/templates/content/series.html
+++ b/src/aividio/web/templates/content/series.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Series - VIDMATION{% endblock %}
+{% block title %}Series - AIVIDIO{% endblock %}
{% block nav_series %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/dashboard.html b/src/aividio/web/templates/dashboard.html
similarity index 99%
rename from src/vidmation/web/templates/dashboard.html
rename to src/aividio/web/templates/dashboard.html
index 60a45eb..210ff78 100644
--- a/src/vidmation/web/templates/dashboard.html
+++ b/src/aividio/web/templates/dashboard.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Dashboard - VIDMATION{% endblock %}
+{% block title %}Dashboard - AIVIDIO{% endblock %}
{% block nav_dashboard %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/jobs/detail.html b/src/aividio/web/templates/jobs/detail.html
similarity index 98%
rename from src/vidmation/web/templates/jobs/detail.html
rename to src/aividio/web/templates/jobs/detail.html
index e74f669..422259f 100644
--- a/src/vidmation/web/templates/jobs/detail.html
+++ b/src/aividio/web/templates/jobs/detail.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Job {{ job.id[:8] }} - VIDMATION{% endblock %}
+{% block title %}Job {{ job.id[:8] }} - AIVIDIO{% endblock %}
{% block nav_jobs %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/jobs/list.html b/src/aividio/web/templates/jobs/list.html
similarity index 98%
rename from src/vidmation/web/templates/jobs/list.html
rename to src/aividio/web/templates/jobs/list.html
index 9a4b99d..cb82041 100644
--- a/src/vidmation/web/templates/jobs/list.html
+++ b/src/aividio/web/templates/jobs/list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Jobs - VIDMATION{% endblock %}
+{% block title %}Jobs - AIVIDIO{% endblock %}
{% block nav_jobs %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/notifications/center.html b/src/aividio/web/templates/notifications/center.html
similarity index 99%
rename from src/vidmation/web/templates/notifications/center.html
rename to src/aividio/web/templates/notifications/center.html
index b18f550..25796b2 100644
--- a/src/vidmation/web/templates/notifications/center.html
+++ b/src/aividio/web/templates/notifications/center.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Notifications - VIDMATION{% endblock %}
+{% block title %}Notifications - AIVIDIO{% endblock %}
{% block nav_notifications %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/schedule/dashboard.html b/src/aividio/web/templates/schedule/dashboard.html
similarity index 99%
rename from src/vidmation/web/templates/schedule/dashboard.html
rename to src/aividio/web/templates/schedule/dashboard.html
index e01097b..7e69d9d 100644
--- a/src/vidmation/web/templates/schedule/dashboard.html
+++ b/src/aividio/web/templates/schedule/dashboard.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Schedule - VIDMATION{% endblock %}
+{% block title %}Schedule - AIVIDIO{% endblock %}
{% block nav_schedule %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/videos/detail.html b/src/aividio/web/templates/videos/detail.html
similarity index 99%
rename from src/vidmation/web/templates/videos/detail.html
rename to src/aividio/web/templates/videos/detail.html
index e05f711..f4c84b1 100644
--- a/src/vidmation/web/templates/videos/detail.html
+++ b/src/aividio/web/templates/videos/detail.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}{{ video.title or 'Video' }} - VIDMATION{% endblock %}
+{% block title %}{{ video.title or 'Video' }} - AIVIDIO{% endblock %}
{% block nav_videos %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/videos/list.html b/src/aividio/web/templates/videos/list.html
similarity index 99%
rename from src/vidmation/web/templates/videos/list.html
rename to src/aividio/web/templates/videos/list.html
index 3843d6a..3f786ac 100644
--- a/src/vidmation/web/templates/videos/list.html
+++ b/src/aividio/web/templates/videos/list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Videos - VIDMATION{% endblock %}
+{% block title %}Videos - AIVIDIO{% endblock %}
{% block nav_videos %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/videos/new.html b/src/aividio/web/templates/videos/new.html
similarity index 99%
rename from src/vidmation/web/templates/videos/new.html
rename to src/aividio/web/templates/videos/new.html
index aae4d87..16ebc9e 100644
--- a/src/vidmation/web/templates/videos/new.html
+++ b/src/aividio/web/templates/videos/new.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}New Video - VIDMATION{% endblock %}
+{% block title %}New Video - AIVIDIO{% endblock %}
{% block nav_videos %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/voices/clone.html b/src/aividio/web/templates/voices/clone.html
similarity index 99%
rename from src/vidmation/web/templates/voices/clone.html
rename to src/aividio/web/templates/voices/clone.html
index d69327b..9cc1432 100644
--- a/src/vidmation/web/templates/voices/clone.html
+++ b/src/aividio/web/templates/voices/clone.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Clone Voice - VIDMATION{% endblock %}
+{% block title %}Clone Voice - AIVIDIO{% endblock %}
{% block nav_voices %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templates/voices/list.html b/src/aividio/web/templates/voices/list.html
similarity index 99%
rename from src/vidmation/web/templates/voices/list.html
rename to src/aividio/web/templates/voices/list.html
index 8e7273d..b527fbe 100644
--- a/src/vidmation/web/templates/voices/list.html
+++ b/src/aividio/web/templates/voices/list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% block title %}Voices - VIDMATION{% endblock %}
+{% block title %}Voices - AIVIDIO{% endblock %}
{% block nav_voices %}bg-white/[0.06] text-white{% endblock %}
{% block breadcrumb %}
diff --git a/src/vidmation/web/templating.py b/src/aividio/web/templating.py
similarity index 100%
rename from src/vidmation/web/templating.py
rename to src/aividio/web/templating.py
diff --git a/src/vidmation/__init__.py b/src/vidmation/__init__.py
deleted file mode 100644
index 04de35c..0000000
--- a/src/vidmation/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""VIDMATION - AI-powered faceless YouTube video automation platform."""
-
-__version__ = "0.1.0"
diff --git a/src/vidmation/__main__.py b/src/vidmation/__main__.py
deleted file mode 100644
index 5781388..0000000
--- a/src/vidmation/__main__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""Allow running as `python -m vidmation`."""
-
-from vidmation.cli.app import app
-
-app()
diff --git a/src/vidmation/analytics/__init__.py b/src/vidmation/analytics/__init__.py
deleted file mode 100644
index a23231a..0000000
--- a/src/vidmation/analytics/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""Analytics module - usage tracking, cost monitoring, and reporting."""
-
-from vidmation.analytics.reports import ReportGenerator
-from vidmation.analytics.tracker import UsageTracker, get_tracker
-from vidmation.analytics.youtube_analytics import YouTubeAnalyticsFetcher
-
-__all__ = [
- "UsageTracker",
- "get_tracker",
- "YouTubeAnalyticsFetcher",
- "ReportGenerator",
-]
diff --git a/src/vidmation/api/__init__.py b/src/vidmation/api/__init__.py
deleted file mode 100644
index 4908e12..0000000
--- a/src/vidmation/api/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""VIDMATION public REST API — authentication, webhooks, and versioned routes."""
diff --git a/src/vidmation/api/v1/__init__.py b/src/vidmation/api/v1/__init__.py
deleted file mode 100644
index a209707..0000000
--- a/src/vidmation/api/v1/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""VIDMATION API v1 — versioned endpoint package."""
diff --git a/src/vidmation/cli/__init__.py b/src/vidmation/cli/__init__.py
deleted file mode 100644
index d96ffa0..0000000
--- a/src/vidmation/cli/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""VIDMATION CLI — command-line interface powered by Typer + Rich."""
diff --git a/src/vidmation/config/__init__.py b/src/vidmation/config/__init__.py
deleted file mode 100644
index e37996b..0000000
--- a/src/vidmation/config/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Configuration system for VIDMATION."""
-
-from vidmation.config.profiles import ChannelProfile, load_profile
-from vidmation.config.settings import Settings, get_settings
-
-__all__ = ["Settings", "get_settings", "ChannelProfile", "load_profile"]
diff --git a/src/vidmation/content/__init__.py b/src/vidmation/content/__init__.py
deleted file mode 100644
index 863aad2..0000000
--- a/src/vidmation/content/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""Content planning and scheduling for automated video production."""
-
-from vidmation.content.calendar import ContentCalendar
-from vidmation.content.planner import ContentPlanner
-from vidmation.content.series import SeriesManager
-
-__all__ = ["ContentPlanner", "ContentCalendar", "SeriesManager"]
diff --git a/src/vidmation/effects/__init__.py b/src/vidmation/effects/__init__.py
deleted file mode 100644
index 31fad3b..0000000
--- a/src/vidmation/effects/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""Post-production effects — auto-zoom, silence removal, B-roll, emoji/SFX, and magic clips."""
-
-from vidmation.effects.emoji_sfx import EmojiSFXEngine
-from vidmation.effects.magic_broll import MagicBRoll
-from vidmation.effects.magic_clips import MagicClips
-from vidmation.effects.magic_zoom import MagicZoom
-from vidmation.effects.silence_remover import SilenceRemover
-
-__all__ = [
- "EmojiSFXEngine",
- "MagicBRoll",
- "MagicClips",
- "MagicZoom",
- "SilenceRemover",
-]
diff --git a/src/vidmation/models/__init__.py b/src/vidmation/models/__init__.py
deleted file mode 100644
index 76e29ac..0000000
--- a/src/vidmation/models/__init__.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Database models for VIDMATION."""
-
-from vidmation.models.analytics import (
- CostSummary,
- OperationType,
- PeriodType,
- ServiceType,
- UsageEvent,
- VideoAnalytics,
-)
-from vidmation.models.api_key import APIKey
-from vidmation.models.asset import Asset, AssetSource, AssetType
-from vidmation.models.base import Base
-from vidmation.models.channel import Channel
-from vidmation.models.job import Job, JobStatus, JobType
-from vidmation.models.notification import Notification
-from vidmation.models.schedule import Schedule, ScheduleStatus, ScheduleType, TopicSource
-from vidmation.models.user import SubscriptionTier, User
-from vidmation.models.video import Video, VideoFormat, VideoStatus
-from vidmation.models.voice import Voice
-from vidmation.models.webhook import Webhook
-
-__all__ = [
- "Base",
- "Channel",
- "Video",
- "VideoFormat",
- "VideoStatus",
- "Job",
- "JobStatus",
- "JobType",
- "Asset",
- "AssetType",
- "AssetSource",
- "APIKey",
- "Webhook",
- "Notification",
- "Schedule",
- "ScheduleStatus",
- "ScheduleType",
- "TopicSource",
- "UsageEvent",
- "CostSummary",
- "VideoAnalytics",
- "ServiceType",
- "OperationType",
- "PeriodType",
- "Voice",
- "User",
- "SubscriptionTier",
-]
diff --git a/src/vidmation/pipeline/__init__.py b/src/vidmation/pipeline/__init__.py
deleted file mode 100644
index 483e446..0000000
--- a/src/vidmation/pipeline/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Pipeline orchestration for VIDMATION video generation."""
-
-from vidmation.pipeline.context import PipelineContext
-from vidmation.pipeline.orchestrator import PipelineOrchestrator
-
-__all__ = ["PipelineContext", "PipelineOrchestrator"]
diff --git a/src/vidmation/queue/__init__.py b/src/vidmation/queue/__init__.py
deleted file mode 100644
index 03af36e..0000000
--- a/src/vidmation/queue/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Job queue system for VIDMATION — worker, task helpers, and scheduler."""
-
-from vidmation.queue.tasks import enqueue_video
-from vidmation.queue.worker import JobWorker
-
-__all__ = ["JobWorker", "enqueue_video"]
diff --git a/src/vidmation/services/__init__.py b/src/vidmation/services/__init__.py
deleted file mode 100644
index 2b05a97..0000000
--- a/src/vidmation/services/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""Service layer — API integrations for video production pipeline.
-
-Each sub-package exposes a factory function that returns the correct
-implementation based on application settings or an explicit provider name.
-"""
-
-from __future__ import annotations
-
-from vidmation.services.base import BaseService
-from vidmation.services.captions.whisper import WhisperCaptionGenerator
-from vidmation.services.imagegen import create_image_generator
-from vidmation.services.media import create_media_provider
-from vidmation.services.scriptgen import create_script_generator
-from vidmation.services.tts import create_tts_provider
-from vidmation.services.youtube.auth import get_credentials
-from vidmation.services.youtube.uploader import YouTubeUploader
-
-__all__ = [
- "BaseService",
- "WhisperCaptionGenerator",
- "create_image_generator",
- "create_media_provider",
- "create_script_generator",
- "create_tts_provider",
- "get_credentials",
- "YouTubeUploader",
-]
diff --git a/src/vidmation/utils/__init__.py b/src/vidmation/utils/__init__.py
deleted file mode 100644
index 1b0f714..0000000
--- a/src/vidmation/utils/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Utility modules for VIDMATION."""
diff --git a/src/vidmation/web/__init__.py b/src/vidmation/web/__init__.py
deleted file mode 100644
index 5d9f916..0000000
--- a/src/vidmation/web/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""VIDMATION web dashboard."""
diff --git a/tests/conftest.py b/tests/conftest.py
index 518e51b..684cfac 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,4 @@
-"""Shared test fixtures for VIDMATION."""
+"""Shared test fixtures for AIVIDIO."""
import os
import tempfile
@@ -8,13 +8,13 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
-from vidmation.models.base import Base
+from aividio.models.base import Base
@pytest.fixture
def tmp_dir():
"""Create a temporary directory for test artifacts."""
- with tempfile.TemporaryDirectory(prefix="vidmation_test_") as d:
+ with tempfile.TemporaryDirectory(prefix="aividio_test_") as d:
yield Path(d)
diff --git a/tests/test_cli/test_cli_help.py b/tests/test_cli/test_cli_help.py
index 9f6dbde..fcf6cb6 100644
--- a/tests/test_cli/test_cli_help.py
+++ b/tests/test_cli/test_cli_help.py
@@ -2,16 +2,16 @@
from typer.testing import CliRunner
-from vidmation.cli.app import app
+from aividio.cli.app import app
runner = CliRunner()
class TestCLIHelp:
- def test_vidmation_help_exits_0(self):
+ def test_aividio_help_exits_0(self):
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "vidmation" in result.output.lower() or "AI-powered" in result.output
+ assert "aividio" in result.output.lower() or "AI-powered" in result.output
def test_generate_help_exits_0(self):
result = runner.invoke(app, ["generate", "--help"])
diff --git a/tests/test_pipeline/test_context.py b/tests/test_pipeline/test_context.py
index d268a3f..60c5fa7 100644
--- a/tests/test_pipeline/test_context.py
+++ b/tests/test_pipeline/test_context.py
@@ -5,9 +5,9 @@
import pytest
-from vidmation.config.profiles import ChannelProfile
-from vidmation.models.video import VideoFormat
-from vidmation.pipeline.context import PipelineContext
+from aividio.config.profiles import ChannelProfile
+from aividio.models.video import VideoFormat
+from aividio.pipeline.context import PipelineContext
@pytest.fixture
diff --git a/tests/test_services/test_batch_csv.py b/tests/test_services/test_batch_csv.py
index f334dcc..3285a69 100644
--- a/tests/test_services/test_batch_csv.py
+++ b/tests/test_services/test_batch_csv.py
@@ -2,7 +2,7 @@
import pytest
-from vidmation.batch.csv_parser import BatchCSVParser, BatchRow
+from aividio.batch.csv_parser import BatchCSVParser, BatchRow
@pytest.fixture
diff --git a/tests/test_services/test_captions.py b/tests/test_services/test_captions.py
index a06617f..885b967 100644
--- a/tests/test_services/test_captions.py
+++ b/tests/test_services/test_captions.py
@@ -2,8 +2,8 @@
import pytest
-from vidmation.captions.animator import CaptionAnimator
-from vidmation.captions.templates import (
+from aividio.captions.animator import CaptionAnimator
+from aividio.captions.templates import (
TEMPLATES,
CaptionTemplate,
create_custom_template,
diff --git a/tests/test_services/test_config.py b/tests/test_services/test_config.py
index 02df9b0..8d59654 100644
--- a/tests/test_services/test_config.py
+++ b/tests/test_services/test_config.py
@@ -5,7 +5,7 @@
import pytest
import yaml
-from vidmation.config.profiles import (
+from aividio.config.profiles import (
ChannelProfile,
ContentConfig,
MusicConfig,
@@ -16,18 +16,18 @@
get_default_profile,
load_profile,
)
-from vidmation.config.settings import Settings
+from aividio.config.settings import Settings
class TestSettings:
def test_settings_loads_defaults(self, monkeypatch):
# Prevent .env file from overriding code defaults
- monkeypatch.delenv("VIDMATION_DEFAULT_LLM_PROVIDER", raising=False)
- monkeypatch.delenv("VIDMATION_DEFAULT_TTS_PROVIDER", raising=False)
- monkeypatch.delenv("VIDMATION_DEFAULT_IMAGE_PROVIDER", raising=False)
- monkeypatch.delenv("VIDMATION_DEFAULT_VIDEO_FORMAT", raising=False)
+ monkeypatch.delenv("AIVIDIO_DEFAULT_LLM_PROVIDER", raising=False)
+ monkeypatch.delenv("AIVIDIO_DEFAULT_TTS_PROVIDER", raising=False)
+ monkeypatch.delenv("AIVIDIO_DEFAULT_IMAGE_PROVIDER", raising=False)
+ monkeypatch.delenv("AIVIDIO_DEFAULT_VIDEO_FORMAT", raising=False)
settings = Settings(_env_file=None)
- assert settings.database_url == "sqlite:///data/vidmation.db"
+ assert settings.database_url == "sqlite:///data/aividio.db"
assert settings.web_port == 8000
assert settings.default_llm_provider == "claude"
assert settings.default_tts_provider == "elevenlabs"
diff --git a/tests/test_services/test_repos.py b/tests/test_services/test_repos.py
index 2d70607..1352993 100644
--- a/tests/test_services/test_repos.py
+++ b/tests/test_services/test_repos.py
@@ -2,9 +2,9 @@
import pytest
-from vidmation.db.repos import ChannelRepo, JobRepo, VideoRepo
-from vidmation.models.job import JobStatus, JobType
-from vidmation.models.video import VideoFormat, VideoStatus
+from aividio.db.repos import ChannelRepo, JobRepo, VideoRepo
+from aividio.models.job import JobStatus, JobType
+from aividio.models.video import VideoFormat, VideoStatus
class TestChannelRepo:
diff --git a/tests/test_video/test_formats.py b/tests/test_video/test_formats.py
index a615863..5a100e8 100644
--- a/tests/test_video/test_formats.py
+++ b/tests/test_video/test_formats.py
@@ -2,7 +2,7 @@
import pytest
-from vidmation.video.formats import (
+from aividio.video.formats import (
FORMAT_REGISTRY,
LANDSCAPE,
PORTRAIT,