diff --git a/.github/workflows/deploy-ec2.yml b/.github/workflows/deploy-ec2.yml index a610fd4..2b05401 100644 --- a/.github/workflows/deploy-ec2.yml +++ b/.github/workflows/deploy-ec2.yml @@ -1,130 +1,162 @@ -name: Deploy to EC2 +name: build-deploy-junohub-ec2 + +env: + IMAGE_NAME: junohub-app + CONTAINER_NAME: junohub-app + # Directory on the EC2 instance where app + compose live + REMOTE_DIR: /junohub on: + workflow_dispatch: push: branches: - main - -permissions: - contents: read + paths-ignore: + - '*.md' + - '.gitignore' + - 'docs/**' + - 'README.md' jobs: - build-and-deploy: - runs-on: ubuntu-latest - - env: - NODE_ENV: production - EC2_ENV: ${{ secrets.EC2_ENV }} - + build-app: + runs-on: ubuntu-22.04 steps: - - name: Checkout repository + - name: Checkout code uses: actions/checkout@v4 - - name: Use Node.js - uses: actions/setup-node@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build application image + uses: docker/build-push-action@v6 with: - node-version: '20' - cache: 'npm' + context: . + file: Dockerfile + push: false + load: true + tags: ${{ env.IMAGE_NAME }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max - - name: Deploy to EC2 with rollback - uses: appleboy/ssh-action@v1.2.0 - env: - EC2_ENV: ${{ secrets.EC2_ENV }} + - name: Save application image + run: | + docker save -o junohub-app_latest.tar ${{ env.IMAGE_NAME }}:latest + echo "✅ Saved application image" + + - name: Upload application image + uses: actions/upload-artifact@v4 with: - host: ${{ secrets.EC2_HOST }} - username: ${{ secrets.EC2_USER }} - key: ${{ secrets.EC2_SSH_KEY }} - port: ${{ secrets.EC2_SSH_PORT || '22' }} - script: | - set -e + name: app-image + path: junohub-app_latest.tar - APP_DIR="${APP_DIR:-/home/ubuntu/JUNOHUB}" + deploy: + runs-on: ubuntu-22.04 + needs: [build-app] + steps: + - name: Checkout code + uses: actions/checkout@v4 - echo "Using app directory: $APP_DIR" - # Create directory with sudo if needed, then ensure ownership - sudo mkdir -p "$APP_DIR" || mkdir -p "$APP_DIR" - sudo chown -R $USER:$USER "$APP_DIR" || true - cd "$APP_DIR" + - name: Download application image + uses: actions/download-artifact@v4 + with: + name: app-image + path: docker - # Initialize git repo if needed - if [ ! -d ".git" ]; then - echo "Cloning repository for the first time..." - git clone ${{ github.repositoryUrl }} . - fi - - git remote set-url origin ${{ github.repositoryUrl }} - - PREV_COMMIT=$(git rev-parse HEAD || echo "") - echo "Previous commit: $PREV_COMMIT" - - echo "Fetching latest changes..." - git fetch origin - git checkout ${{ github.ref_name }} - git reset --hard ${{ github.sha }} - - echo "Writing .env file from EC2_ENV secret..." - # Write .env file AFTER pulling latest code so it's in the right location - # EC2_ENV is provided by GitHub as a multi-line secret matching your local .env - printf '%s\n' "$EC2_ENV" > .env - - echo "Checking Docker and Docker Compose..." - if ! command -v docker >/dev/null 2>&1; then - echo "Error: Docker is not installed" - exit 1 - fi - - if command -v docker-compose >/dev/null 2>&1; then - DC="sudo docker-compose" - elif sudo docker compose version >/dev/null 2>&1; then - DC="sudo docker compose" - else - echo "Error: Docker Compose is not installed" - exit 1 - fi - - echo "Using: $DC" - $DC version - - echo "Stopping existing containers..." - $DC down || true - - echo "Building and starting containers with docker-compose..." - set +e - $DC up -d --build - DEPLOY_EXIT=$? - set -e - - if [ $DEPLOY_EXIT -ne 0 ]; then - echo "Deployment failed, attempting rollback..." - if [ -n "$PREV_COMMIT" ]; then - git reset --hard "$PREV_COMMIT" - printf '%s\n' "$EC2_ENV" > .env - $DC up -d --build || true - fi - exit 1 - fi - - echo "Waiting for containers to be healthy..." - sleep 10 - - echo "Running health check..." - HEALTH_URL="${HEALTH_URL:-http://localhost:3000/api/health}" - - for i in {1..15}; do - if curl -fsS "$HEALTH_URL" >/dev/null 2>&1; then - echo "Health check passed on attempt $i." - echo "Deployment successful!" - exit 0 - fi - echo "Health check attempt $i failed, retrying in 5 seconds..." - sleep 5 - done - - echo "Health check failed after multiple attempts, rolling back..." - if [ -n "$PREV_COMMIT" ]; then - git reset --hard "$PREV_COMMIT" - printf '%s\n' "$EC2_ENV" > .env - $DC up -d --build || true - fi + - name: Verify image tar + run: | + if [ ! -f docker/junohub-app_latest.tar ]; then + echo "ERROR: Application image tar missing after download" + ls -la docker/ || true exit 1 + fi + echo "✅ Application image tar ready for deploy" + - name: Set up environment file + run: | + # Base env for production container + echo "IMAGE_NAME=${{ env.IMAGE_NAME }}" > .env + echo "CONTAINER_NAME=${{ env.CONTAINER_NAME }}" >> .env + echo "NODE_ENV=production" >> .env + + # Append project-specific environment from EC2_ENV secret + # EC2_ENV should contain additional KEY=VALUE lines + echo "${{ secrets.EC2_ENV }}" >> .env + + echo "Environment file created (sensitive values masked):" + cat .env | sed 's/\(PASS\|SECRET\|KEY\|TOKEN\|DATABASE_URL\)=.*/\1=***MASKED***/g' || true + + - name: Setup SSH + run: | + # Set up the SSH key from GitHub Secrets + echo "${{ secrets.EC2_SSH_KEY }}" | tr -d '\r' > ssh_key.pem + chmod 400 ssh_key.pem + + - name: Setup directories on server + run: | + ssh -i ssh_key.pem -o StrictHostKeyChecking=no ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'ENDSSH' + # Create directory for docker-compose file, .env and image tar + mkdir -p ${{ env.REMOTE_DIR }} + echo "✅ Remote directory ensured at ${{ env.REMOTE_DIR }}" + ENDSSH + + - name: Copy deployment files to server + run: | + # Copy docker-compose (app + Postgres), and .env + scp -i ssh_key.pem -o StrictHostKeyChecking=no docker-compose.yml ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${{ env.REMOTE_DIR }}/ + scp -i ssh_key.pem -o StrictHostKeyChecking=no .env ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${{ env.REMOTE_DIR }}/ + + # Copy application image tar + echo "Copying Docker image to server..." + scp -i ssh_key.pem -o StrictHostKeyChecking=no docker/junohub-app_latest.tar ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${{ env.REMOTE_DIR }}/ + echo "✅ Successfully copied Docker image to server" + + - name: Deploy with rolling update + run: | + ssh -i ssh_key.pem -o StrictHostKeyChecking=no ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'ENDSSH' + set -e + cd ${{ env.REMOTE_DIR }} + + # Basic disk space check (do not touch volumes) + echo "=== DISK SPACE CHECK ===" + df -h + AVAILABLE_SPACE=\$(df -BG / | tail -1 | awk '{print \$4}' | sed 's/G//') + if [ "\$AVAILABLE_SPACE" -lt 5 ]; then + echo "⚠️ Low disk space: \${AVAILABLE_SPACE}GB available. Cleaning up dangling images..." + docker system prune -af --volumes=false || true + docker image prune -af || true + df -h + fi + + # Verify Docker image tar exists + if [ ! -f junohub-app_latest.tar ]; then + echo "ERROR: No application image tar found. Cannot deploy without pre-built image." + exit 1 + fi + + echo "=== LOADING APPLICATION DOCKER IMAGE ===" + docker load -i junohub-app_latest.tar + rm -f junohub-app_latest.tar + echo "✅ Application image loaded" + + # Rolling update with docker compose (app + Postgres) + echo "=== ROLLING UPDATE (docker compose up -d) ===" + if ! docker compose -f docker-compose.yml --env-file .env up -d --remove-orphans; then + echo "ERROR: Deployment failed" + docker compose -f docker-compose.yml down || true + exit 1 + fi + + echo "✅ Deployment successful! Current services:" + docker compose -f docker-compose.yml --env-file .env ps + + # Final cleanup (preserve volumes; prune dangling images only) + docker system prune -f --volumes=false || true + ENDSSH + + - name: Local cleanup + if: always() + run: | + rm -f ssh_key.pem + rm -f .env + rm -f docker/*.tar || true + docker system prune -f || true \ No newline at end of file diff --git a/.gitignore b/.gitignore index e1ab46a..93bb99f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,7 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -.env* +.env # vercel .vercel diff --git a/Dockerfile b/Dockerfile index 523f0b1..f73b2cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,10 +15,12 @@ COPY . . # Build the Next.js application RUN npm run build -# Remove dev dependencies to reduce image size -RUN npm prune --production +# Copy and set up entrypoint script +COPY docker-entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/docker-entrypoint.sh EXPOSE 3000 -# Use production start command +# Use entrypoint to run migrations before starting the app +ENTRYPOINT ["docker-entrypoint.sh"] CMD ["npm", "start"] diff --git a/Documents/GMAIL_WEBHOOK_SETUP.md b/Documents/GMAIL_WEBHOOK_SETUP.md new file mode 100644 index 0000000..38ecabc --- /dev/null +++ b/Documents/GMAIL_WEBHOOK_SETUP.md @@ -0,0 +1,154 @@ +# Gmail Webhook Setup Guide + +This guide explains how to set up automatic email detection and AI-powered replies using Gmail push notifications. + +## Overview + +When a customer sends an email to your Gmail account, Gmail will send a push notification to our webhook endpoint. The webhook then: +1. Fetches the incoming email +2. Analyzes it using your store's knowledge base +3. Generates an AI-powered reply +4. Sends the reply automatically + +## Prerequisites + +1. **Gmail Integration**: Your Gmail account must be connected via `/dashboard/juno-engine/gmail-connect` +2. **Knowledge Base**: Your store must have FAQs or documents in the knowledge base +3. **Google Cloud Pub/Sub**: Required for Gmail push notifications + +## Step 1: Set Up Google Cloud Pub/Sub + +Gmail push notifications require Google Cloud Pub/Sub. Follow these steps: + +### 1.1 Create a Pub/Sub Topic + +1. Go to [Google Cloud Console](https://console.cloud.google.com/) +2. Navigate to **Pub/Sub** > **Topics** +3. Click **Create Topic** +4. Name it `gmail-notifications` (or any name you prefer) +5. Click **Create** + +### 1.2 Create a Subscription + +1. In the topic page, click **Create Subscription** +2. Name it `gmail-webhook-subscription` +3. Choose **Push** delivery type +4. Set the **Endpoint URL** to: `https://yourdomain.com/api/webhooks/gmail` + - Replace `yourdomain.com` with your actual domain + - For local development with ngrok: `https://your-ngrok-url.ngrok-free.dev/api/webhooks/gmail` +5. Click **Create** + +### 1.3 Grant Gmail API Access + +1. Go to **IAM & Admin** > **Service Accounts** +2. Find or create a service account for Gmail API +3. Grant it the `pubsub.topics.publish` permission on your topic + +### 1.4 Get Topic Name + +Copy the full topic name in the format: +``` +projects/YOUR_PROJECT_ID/topics/gmail-notifications +``` + +## Step 2: Configure Environment Variables + +Add the Pub/Sub topic name to your `.env` file: + +```env +GMAIL_PUBSUB_TOPIC=projects/YOUR_PROJECT_ID/topics/gmail-notifications +``` + +## Step 3: Enable Gmail Watch + +Once Pub/Sub is set up, enable Gmail push notifications: + +### Option A: Via API + +Make a POST request to: +``` +POST /api/integrations/gmail/watch +``` + +This will set up Gmail to send push notifications to your webhook. + +### Option B: Via UI (Recommended) + +Add a button in the Gmail connect page to enable webhooks (coming soon). + +## Step 4: Verify Webhook + +Gmail will send a verification request to your webhook endpoint. The endpoint automatically handles verification by returning the challenge token. + +## How It Works + +### Webhook Flow + +1. **Customer sends email** → Gmail receives it +2. **Gmail sends push notification** → Webhook endpoint receives notification +3. **Webhook processes email**: + - Fetches the email content + - Extracts sender, subject, and body + - Searches knowledge base for relevant information + - Generates AI-powered reply using Bedrock +4. **Reply is sent** → Customer receives automated response +5. **Email is marked as read** → Prevents duplicate processing + +### Email Processing Logic + +- Only processes emails from external senders (not same domain) +- Uses knowledge base to find relevant answers +- Generates contextual replies using AI +- Maintains email threading (replies are properly threaded) +- Logs all sent emails for tracking + +## Monitoring + +Check logs for: +- Webhook notifications received +- Emails processed +- Replies sent +- Any errors during processing + +## Troubleshooting + +### Webhook Not Receiving Notifications + +1. Verify Pub/Sub topic exists and is configured correctly +2. Check that Gmail watch is active (watch expires after 7 days) +3. Verify webhook URL is accessible (not behind firewall) +4. Check Google Cloud Console logs for Pub/Sub errors + +### Replies Not Being Sent + +1. Verify Gmail integration is connected +2. Check that store has active knowledge base content +3. Review error logs for specific issues +4. Ensure Bedrock API credentials are configured + +### Watch Expired + +Gmail watch requests expire after 7 days. You need to renew them: +- Call `/api/integrations/gmail/watch` again +- Or set up a cron job to renew automatically + +## Security + +- Webhook endpoint verifies Gmail notifications +- Only processes emails for authenticated users +- Validates store ownership before processing +- Logs all actions for audit trail + +## Limitations + +- Gmail watch expires after 7 days (needs renewal) +- Only processes unread emails in inbox +- Requires active knowledge base for quality replies +- May have rate limits from Gmail API + +## Next Steps + +- Set up automatic watch renewal (cron job) +- Add email filtering rules (only reply to certain senders) +- Implement reply templates +- Add analytics dashboard for email metrics diff --git a/README.md b/README.md index 9e88546..5251d86 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The frontend and backend are implemented in a single Next.js App Router project. - **Background / Integrations**: - Gmail APIs (via OAuth) - Shopify APIs - - Gemini for embeddings and chat + - AWS Bedrock for embeddings and chat (Titan Text Embeddings V2 + Claude 3 Haiku) - **Deployment**: Docker + GitHub Actions → AWS EC2 --- @@ -48,9 +48,9 @@ Copy `.env.example` (or the variables listed below) into a local `.env` file and - `SMTP_SECURE` - `SMTP_USER` - `SMTP_PASSWORD` -- `GEMINI_API_KEY` -- `GEMINI_EMBEDDING_MODEL` -- `GEMINI_CHAT_MODEL` +- `AWS_ACCESS_KEY_ID` (for AWS Bedrock) +- `AWS_SECRET_ACCESS_KEY` (for AWS Bedrock) +- `AWS_REGION` (default: `us-east-1`) - `ADMIN_EMAIL` - `ADMIN_PASSWORD` @@ -119,6 +119,119 @@ This gives you a sharp deployment with an automatic rollback path. --- +## AWS Bedrock Setup + +The knowledge base feature uses AWS Bedrock for AI-powered embeddings and chat completion. + +### Prerequisites + +1. **AWS Account**: Create an account at https://aws.amazon.com/ +2. **Enable Bedrock Access**: + - Go to https://console.aws.amazon.com/bedrock/ + - Navigate to "Model access" in the left sidebar + - Request access to: + - **Amazon Titan Text Embeddings V2** (for embeddings) + - **Anthropic Claude 3 Haiku** (for chat completion) + - Wait for approval (usually instant for Titan, may take a few hours for Claude) + +### IAM Setup + +Create an IAM user or role with Bedrock permissions: + +1. Go to IAM Console → Users (or Roles) +2. Create a new user/role or select existing +3. Attach the following policy (or create a custom policy): + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["bedrock:InvokeModel"], + "Resource": [ + "arn:aws:bedrock:*::foundation-model/amazon.titan-embed-text-v2:0", + "arn:aws:bedrock:*::foundation-model/anthropic.claude-3-haiku-20240307-v1:0" + ] + } + ] +} +``` + +4. **Create Access Keys** (for IAM user): + - Go to Security credentials tab + - Click "Create access key" + - Choose "Application running outside AWS" or "EC2" depending on your deployment + - Copy the Access Key ID and Secret Access Key + - Add these to your `.env` file + +### Environment Variables + +Add to your `.env` file: + +```bash +AWS_ACCESS_KEY_ID="your-access-key-id" +AWS_SECRET_ACCESS_KEY="your-secret-access-key" +AWS_REGION="us-east-1" # or your preferred region +``` + +### Database Migration + +After setting up AWS Bedrock, you need to migrate existing embeddings: + +1. **Run the database migration** to change embedding dimensions from 1536 to 1024: + ```bash + npx prisma migrate deploy + ``` + +2. **Re-embed existing knowledge base data**: + ```bash + npm run re-embed-knowledge-base + ``` + + This script will: + - Read all existing `KnowledgeChunk` records + - Generate new 1024-dimension embeddings using Bedrock Titan V2 + - Update the database with new embeddings + - Process in batches to avoid rate limiting + +### Models Used + +- **Embeddings**: Amazon Titan Text Embeddings V2 (`amazon.titan-embed-text-v2:0`) + - Output dimension: 1024 (configurable: 256, 512, or 1024) + - Used for: FAQ embeddings, document chunk embeddings, query embeddings + +- **Chat**: Anthropic Claude 3 Haiku (`anthropic.claude-3-haiku-20240307-v1:0`) + - Used for: RAG (Retrieval-Augmented Generation) query responses + - Fast and cost-effective for knowledge base Q&A + +### Optional: Override Models + +You can override the default models via environment variables: + +```bash +BEDROCK_EMBEDDING_MODEL="amazon.titan-embed-text-v2:0" +BEDROCK_CHAT_MODEL="anthropic.claude-3-haiku-20240307-v1:0" +``` + +### Prisma: + +If you want to visualize the database: +``` +export DATABASE_URL='' +npx prisma studio +``` + + +### Troubleshooting + +- **"Access Denied" errors**: Ensure Bedrock model access is enabled in AWS Console +- **"Invalid credentials"**: Verify `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are correct +- **"Model not found"**: Check that you've requested access to the models in Bedrock Console +- **Re-embedding fails**: Ensure AWS credentials are set and Bedrock access is enabled + +--- + ## License Private / All rights reserved unless explicitly stated otherwise. diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..f40217e --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,30 @@ +#!/bin/sh +set -e + +echo "Waiting for database to be ready..." +# Wait for database to be available (already handled by depends_on in docker-compose, but extra safety) +# Use Node.js to check database connectivity since we have pg available +until node -e " +const { Client } = require('pg'); +const client = new Client({ connectionString: process.env.DATABASE_URL }); +client.connect() + .then(() => { client.end(); process.exit(0); }) + .catch(() => { process.exit(1); }); +" 2>/dev/null; do + echo "Database is unavailable - sleeping" + sleep 1 +done + +echo "Syncing database schema with Prisma (db push)..." +npx prisma db push + +if [ "$RUN_TEMPLATE_SEED_ON_START" = "true" ]; then + echo "Seeding predefined email templates..." + # Run seed script; if it fails, log the error but continue starting the app + if ! npx tsx scripts/seed-templates.ts; then + echo "Template seeding failed, continuing without seeded templates." + fi +fi + +echo "Starting application..." +exec "$@" diff --git a/next.config.ts b/next.config.ts index 2b9ca6b..ba78b8d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -16,6 +16,18 @@ const nextConfig: NextConfig = { protocol: "https", hostname: "*.myshopify.com", }, + { + protocol: "https", + hostname: "*.ngrok.com", + }, + { + protocol: "https", + hostname: "*.ngrok.io", + }, + { + protocol: "https", + hostname: "*.ngrok-free.dev", + }, ], }, // Allow ngrok origin for development (fixes cross-origin warnings) diff --git a/package-lock.json b/package-lock.json index b35b8a8..e7a705e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@auth/core": "^0.41.0", "@auth/prisma-adapter": "^2.11.1", + "@aws-sdk/client-bedrock-runtime": "^3.989.0", "@prisma/adapter-pg": "^7.3.0", "@prisma/client": "^7.3.0", "@tailwindcss/postcss": "^4", @@ -17,6 +18,7 @@ "@types/qrcode": "^1.5.6", "@upstash/ratelimit": "^2.0.5", "@upstash/redis": "^1.34.3", + "aws-cli": "^0.0.2", "bcryptjs": "^3.0.3", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -47,6 +49,7 @@ "eslint": "^9", "eslint-config-next": "16.1.6", "tailwindcss": "^4", + "tsx": "^4.21.0", "typescript": "^5" } }, @@ -100,6 +103,708 @@ "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6" } }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.989.0.tgz", + "integrity": "sha512-qVa5B0wXjIuPRhX1dcZo1sa9Y4ycI9tiqK7B4FLok67gUWckiKmEf1xQDFrTmc2eCK5g0CTaeiRdbeM1eWmW1Q==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/credential-provider-node": "^3.972.8", + "@aws-sdk/eventstream-handler-node": "^3.972.5", + "@aws-sdk/middleware-eventstream": "^3.972.3", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.9", + "@aws-sdk/middleware-websocket": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/token-providers": "3.989.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.989.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.7", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.23.0", + "@smithy/eventstream-serde-browser": "^4.2.8", + "@smithy/eventstream-serde-config-resolver": "^4.3.8", + "@smithy/eventstream-serde-node": "^4.2.8", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-stream": "^4.5.12", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.989.0.tgz", + "integrity": "sha512-3sC+J1ru5VFXLgt9KZmXto0M7mnV5RkS6FNGwRMK3XrojSjHso9DLOWjbnXhbNv4motH8vu53L1HK2VC1+Nj5w==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.9", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.989.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.7", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.23.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.9.tgz", + "integrity": "sha512-cyUOfJSizn8da7XrBEFBf4UMI4A6JQNX6ZFcKtYmh/CrwfzsDcabv3k/z0bNwQ3pX5aeq5sg/8Bs/ASiL0bJaA==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.23.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.7.tgz", + "integrity": "sha512-r8kBtglvLjGxBT87l6Lqkh9fL8yJJ6O4CYQPjKlj3AkCuL4/4784x3rxxXWw9LTKXOo114VB6mjxAuy5pI7XIg==", + "dependencies": { + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.9.tgz", + "integrity": "sha512-40caFblEg/TPrp9EpvyMxp4xlJ5TuTI+A8H6g8FhHn2hfH2PObFAPLF9d5AljK/G69E1YtTklkuQeAwPlV3w8Q==", + "dependencies": { + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/types": "^3.973.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.7.tgz", + "integrity": "sha512-zeYKrMwM5bCkHFho/x3+1OL0vcZQ0OhTR7k35tLq74+GP5ieV3juHXTZfa2LVE0Bg75cHIIerpX0gomVOhzo/w==", + "dependencies": { + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/credential-provider-env": "^3.972.7", + "@aws-sdk/credential-provider-http": "^3.972.9", + "@aws-sdk/credential-provider-login": "^3.972.7", + "@aws-sdk/credential-provider-process": "^3.972.7", + "@aws-sdk/credential-provider-sso": "^3.972.7", + "@aws-sdk/credential-provider-web-identity": "^3.972.7", + "@aws-sdk/nested-clients": "3.989.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.7.tgz", + "integrity": "sha512-Q103cLU6OjAllYjX7+V+PKQw654jjvZUkD+lbUUiFbqut6gR5zwl1DrelvJPM5hnzIty7BCaxaRB3KMuz3M/ug==", + "dependencies": { + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/nested-clients": "3.989.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.8.tgz", + "integrity": "sha512-AaDVOT7iNJyLjc3j91VlucPZ4J8Bw+eu9sllRDugJqhHWYyR3Iyp2huBUW8A3+DfHoh70sxGkY92cThAicSzlQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.7", + "@aws-sdk/credential-provider-http": "^3.972.9", + "@aws-sdk/credential-provider-ini": "^3.972.7", + "@aws-sdk/credential-provider-process": "^3.972.7", + "@aws-sdk/credential-provider-sso": "^3.972.7", + "@aws-sdk/credential-provider-web-identity": "^3.972.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.7.tgz", + "integrity": "sha512-hxMo1V3ujWWrQSONxQJAElnjredkRpB6p8SDjnvRq70IwYY38R/CZSys0IbhRPxdgWZ5j12yDRk2OXhxw4Gj3g==", + "dependencies": { + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.7.tgz", + "integrity": "sha512-ZGKBOHEj8Ap15jhG2XMncQmKLTqA++2DVU2eZfLu3T/pkwDyhCp5eZv5c/acFxbZcA/6mtxke+vzO/n+aeHs4A==", + "dependencies": { + "@aws-sdk/client-sso": "3.989.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/token-providers": "3.989.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.7.tgz", + "integrity": "sha512-AbYupBIoSJoVMlbMqBhNvPhqj+CdGtzW7Uk4ZIMBm2br18pc3rkG1VaKVFV85H87QCvLHEnni1idJjaX1wOmIw==", + "dependencies": { + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/nested-clients": "3.989.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.5.tgz", + "integrity": "sha512-xEmd3dnyn83K6t4AJxBJA63wpEoCD45ERFG0XMTViD2E/Ohls9TLxjOWPb1PAxR9/46cKy/TImez1GoqP6xVNQ==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/eventstream-codec": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.3.tgz", + "integrity": "sha512-pbvZ6Ye/Ks6BAZPa3RhsNjHrvxU9li25PMhSdDpbX0jzdpKpAkIR65gXSNKmA/REnSdEMWSD4vKUW+5eMFzB6w==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.9.tgz", + "integrity": "sha512-1g1B7yf7KzessB0mKNiV9gAHEwbM662xgU+VE4LxyGe6kVGZ8LqYsngjhE+Stna09CJ7Pxkjr6Uq1OtbGwJJJg==", + "dependencies": { + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.989.0", + "@smithy/core": "^3.23.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.6.tgz", + "integrity": "sha512-1DedO6N3m8zQ/vG6twNiHtsdwBgk773VdavLEbB3NXeKZDlzSK1BTviqWwvJdKx5UnIy4kGGP6WWpCEFEt/bhQ==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-format-url": "^3.972.3", + "@smithy/eventstream-codec": "^4.2.8", + "@smithy/eventstream-serde-browser": "^4.2.8", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.989.0.tgz", + "integrity": "sha512-Dbk2HMPU3mb6RrSRzgf0WCaWSbgtZG258maCpuN2/ONcAQNpOTw99V5fU5CA1qVK6Vkm4Fwj2cnOnw7wbGVlOw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.9", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.989.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.7", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.23.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.989.0.tgz", + "integrity": "sha512-OdBByMv+OjOZoekrk4THPFpLuND5aIQbDHCGh3n2rvifAbm31+6e0OLhxSeCF1UMPm+nKq12bXYYEoCIx5SQBg==", + "dependencies": { + "@aws-sdk/core": "^3.973.9", + "@aws-sdk/nested-clients": "3.989.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.989.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.989.0.tgz", + "integrity": "sha512-eKmAOeQM4Qusq0jtcbZPiNWky8XaojByKC/n+THbJ8vJf7t4ys8LlcZ4PrBSHZISe9cC484mQsPVOQh6iySjqw==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.3.tgz", + "integrity": "sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz", + "integrity": "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.7.tgz", + "integrity": "sha512-oyhv+FjrgHjP+F16cmsrJzNP4qaRJzkV1n9Lvv4uyh3kLqo3rIe9NSBSBa35f2TedczfG2dD+kaQhHBB47D6Og==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.9", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -405,6 +1110,422 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1292,53 +2413,53 @@ } }, "node_modules/@otplib/core": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.2.1.tgz", - "integrity": "sha512-IyfHvYNCyipDxhmJdcUUvUeT3Hz84/GgM6G2G6BTEmnAKPzNA7U0kYGkxKZWY9h23W94RJk4qiClJRJN5zKGvg==" + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.3.0.tgz", + "integrity": "sha512-pnQDOuCmFVeF/XnboJq9TOJgLoo2idNPJKMymOF8vGqJJ+ReKRYM9bUGjNPRWC0tHjMwu1TXbnzyBp494JgRag==" }, "node_modules/@otplib/hotp": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.2.1.tgz", - "integrity": "sha512-iRKqvj0TnemtXXtEswzBX50Z0yMNa0lH9PSdr5N4CJc1mDEuUmFFZQqnu3PfA3fPd3WeAU+mHgmK/xq18+K1QA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.3.0.tgz", + "integrity": "sha512-XJMZGz2bg4QJwK7ulvl1GUI2VMn/flaIk/E/BTKAejHsX2kUtPF1bRhlZ2+elq8uU5Fs9Z9FHcQK2CPZNQbbUQ==", "dependencies": { - "@otplib/core": "13.2.1", - "@otplib/uri": "13.2.1" + "@otplib/core": "13.3.0", + "@otplib/uri": "13.3.0" } }, "node_modules/@otplib/plugin-base32-scure": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.2.1.tgz", - "integrity": "sha512-vnA2qqgJ/FbFbDNGOLAS8dKfCsJFXwFsZKYklE8yl2INkCOUR0vbVdJ2TVmufzC8R1RRZHW+cDR20ACgc9XFYg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.3.0.tgz", + "integrity": "sha512-/jYbL5S6GB0Ie3XGEWtLIr9s5ZICl/BfmNL7+8/W7usZaUU4GiyLd2S+JGsNCslPyqNekSudD864nDAvRI0s8w==", "dependencies": { - "@otplib/core": "13.2.1", + "@otplib/core": "13.3.0", "@scure/base": "^2.0.0" } }, "node_modules/@otplib/plugin-crypto-noble": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.2.1.tgz", - "integrity": "sha512-Dxjmt4L+5eDWJf5EvbcMp+fxcliyKoB9N9sNQq/vuVAUvq+KiqpiiCQZ/wHyrN0ArB0NdevtK1KByyAq080ldg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.3.0.tgz", + "integrity": "sha512-wmV+jBVncepgwv99G7Plrdzd0tHfbpXk2U+OD7MO7DzpDqOYEgOPi+IIneksJSTL8QvWdfi+uQEuhnER4fKouA==", "dependencies": { "@noble/hashes": "^2.0.1", - "@otplib/core": "13.2.1" + "@otplib/core": "13.3.0" } }, "node_modules/@otplib/totp": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.2.1.tgz", - "integrity": "sha512-LzDzAAK3w8rspF3urBnWjOlxso1SCGxX9Pnu/iy+HkC0y0HgiLsW7jhkr2hJ3u4cyBdL/tOKUhhELwsjyvunwQ==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.3.0.tgz", + "integrity": "sha512-XfjGNoN8d9S3Ove2j7AwkVV7+QDFsV7Lm7YwSiezNaHffkWtJ60aJYpmf+01dARdPST71U2ptueMsRJso4sq4A==", "dependencies": { - "@otplib/core": "13.2.1", - "@otplib/hotp": "13.2.1", - "@otplib/uri": "13.2.1" + "@otplib/core": "13.3.0", + "@otplib/hotp": "13.3.0", + "@otplib/uri": "13.3.0" } }, "node_modules/@otplib/uri": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.2.1.tgz", - "integrity": "sha512-ssYnfiUrFTs/rPRUW8h59m0MVLYOC+UKk7tVGYgtG15lLaLBrNBQjM2YFanuzn9Jm4iv9JxiNG7TRkwcnyR09A==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.3.0.tgz", + "integrity": "sha512-3oh6nBXy+cm3UX9cxEAGZiDrfxHU2gfelYFV+XNCx+8dq39VaQVymwlU2yjPZiMAi/3agaUeEftf2RwM5F+Cyg==", "dependencies": { - "@otplib/core": "13.2.1" + "@otplib/core": "13.3.0" } }, "node_modules/@panva/hkdf": { @@ -1350,21 +2471,21 @@ } }, "node_modules/@prisma/adapter-pg": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.3.0.tgz", - "integrity": "sha512-iuYQMbIPO6i9O45Fv8TB7vWu00BXhCaNAShenqF7gLExGDbnGp5BfFB4yz1K59zQ59jF6tQ9YHrg0P6/J3OoLg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.4.0.tgz", + "integrity": "sha512-LWwTHaio0bMxvzahmpwpWqsZM0vTfMqwF8zo06YvILL/o47voaSfKzCVxZw/o9awf4fRgS5Vgthobikj9Dusaw==", "dependencies": { - "@prisma/driver-adapter-utils": "7.3.0", + "@prisma/driver-adapter-utils": "7.4.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "node_modules/@prisma/client": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.3.0.tgz", - "integrity": "sha512-FXBIxirqQfdC6b6HnNgxGmU7ydCPEPk7maHMOduJJfnTP+MuOGa15X4omjR/zpPUUpm8ef/mEFQjJudOGkXFcQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.4.0.tgz", + "integrity": "sha512-Sc+ncr7+ph1hMf1LQfn6UyEXDEamCd5pXMsx8Q3SBH0NGX+zjqs3eaABt9hXwbcK9l7f8UyK8ldxOWA2LyPynQ==", "dependencies": { - "@prisma/client-runtime-utils": "7.3.0" + "@prisma/client-runtime-utils": "7.4.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -1383,14 +2504,14 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.3.0.tgz", - "integrity": "sha512-dG/ceD9c+tnXATPk8G+USxxYM9E6UdMTnQeQ+1SZUDxTz7SgQcfxEqafqIQHcjdlcNK/pvmmLfSwAs3s2gYwUw==" + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.4.0.tgz", + "integrity": "sha512-jTmWAOBGBSCT8n7SMbpjCpHjELgcDW9GNP/CeK6CeqjUFlEL6dn8Cl81t/NBDjJdXDm85XDJmc+PEQqqQee3xw==" }, "node_modules/@prisma/config": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.3.0.tgz", - "integrity": "sha512-QyMV67+eXF7uMtKxTEeQqNu/Be7iH+3iDZOQZW5ttfbSwBamCSdwPszA0dum+Wx27I7anYTPLmRmMORKViSW1A==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.4.0.tgz", + "integrity": "sha512-EnNrZMwZ9+O6UlG+YO9SP3VhVw4zwMahDRzQm3r0DQn9KeU5NwzmaDAY+BzACrgmaU71Id1/0FtWIDdl7xQp9g==", "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", @@ -1399,9 +2520,9 @@ } }, "node_modules/@prisma/debug": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.3.0.tgz", - "integrity": "sha512-yh/tHhraCzYkffsI1/3a7SHX8tpgbJu1NPnuxS4rEpJdWAUDHUH25F1EDo6PPzirpyLNkgPPZdhojQK804BGtg==" + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.4.0.tgz", + "integrity": "sha512-fZicwzgFHvvPMrRLCUinrsBTdadJsi/1oirzShjmFvNLwtu2DYlkxwRVy5zEGhp85mrEGnLeS/PdNRCdE027+Q==" }, "node_modules/@prisma/dev": { "version": "0.20.0", @@ -1428,54 +2549,54 @@ } }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.3.0.tgz", - "integrity": "sha512-Wdlezh1ck0Rq2dDINkfSkwbR53q53//Eo1vVqVLwtiZ0I6fuWDGNPxwq+SNAIHnsU+FD/m3aIJKevH3vF13U3w==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.4.0.tgz", + "integrity": "sha512-jEyE5LkqZ27Ba/DIOfCGOQl6nKMLxuwJNRceCfh7/LRs46UkIKn3bmkI97MEH2t7zkYV3PGBrUr+6sMJaHvc0A==", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.4.0" } }, "node_modules/@prisma/engines": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.3.0.tgz", - "integrity": "sha512-cWRQoPDXPtR6stOWuWFZf9pHdQ/o8/QNWn0m0zByxf5Kd946Q875XdEJ52pEsX88vOiXUmjuPG3euw82mwQNMg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.4.0.tgz", + "integrity": "sha512-H+dgpbbY3VN/j5hOSVP1LXsv/rU0w/4C2zh5PZUwo/Q3NqZjOvBlVvkhtziioRmeEZ3SBAqPCsf1sQ74sI3O/w==", "hasInstallScript": true, "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/fetch-engine": "7.3.0", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.4.0", + "@prisma/engines-version": "7.4.0-20.ab56fe763f921d033a6c195e7ddeb3e255bdbb57", + "@prisma/fetch-engine": "7.4.0", + "@prisma/get-platform": "7.4.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735.tgz", - "integrity": "sha512-IH2va2ouUHihyiTTRW889LjKAl1CusZOvFfZxCDNpjSENt7g2ndFsK0vdIw/72v7+jCN6YgkHmdAP/BI7SDgyg==" + "version": "7.4.0-20.ab56fe763f921d033a6c195e7ddeb3e255bdbb57", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.4.0-20.ab56fe763f921d033a6c195e7ddeb3e255bdbb57.tgz", + "integrity": "sha512-5o3/bubIYdUeg38cyNf+VDq+LVtxvvi2393Fd1Uru52LPfkGJnmVbCaX1wBOAncgKR3BCloMJFD+Koog9LtYqQ==" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.0.tgz", + "integrity": "sha512-fOUIoGzAPgtjHVs4DsVSnEDPBEauAmFeZr4Ej3tMwxywam7hHdRtCzgKagQBKcYIJuya8gzYrTqUoukzXtWJaA==", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.4.0" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.3.0.tgz", - "integrity": "sha512-Mm0F84JMqM9Vxk70pzfNpGJ1lE4hYjOeLMu7nOOD1i83nvp8MSAcFYBnHqLvEZiA6onUR+m8iYogtOY4oPO5lQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.4.0.tgz", + "integrity": "sha512-IXPOYskT89UTVsntuSnMTiKRWCuTg5JMWflgEDV1OSKFpuhwP5vqbfF01/iwo9y6rCjR0sDIO+jdV5kq38/hgA==", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.4.0", + "@prisma/engines-version": "7.4.0-20.ab56fe763f921d033a6c195e7ddeb3e255bdbb57", + "@prisma/get-platform": "7.4.0" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.0.tgz", + "integrity": "sha512-fOUIoGzAPgtjHVs4DsVSnEDPBEauAmFeZr4Ej3tMwxywam7hHdRtCzgKagQBKcYIJuya8gzYrTqUoukzXtWJaA==", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.4.0" } }, "node_modules/@prisma/get-platform": { @@ -1532,9 +2653,9 @@ } }, "node_modules/@reduxjs/toolkit/node_modules/immer": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", - "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -1554,6 +2675,611 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.0.tgz", + "integrity": "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg==", + "dependencies": { + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.12", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz", + "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz", + "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz", + "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz", + "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz", + "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.14.tgz", + "integrity": "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag==", + "dependencies": { + "@smithy/core": "^3.23.0", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.31", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.31.tgz", + "integrity": "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz", + "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "dependencies": { + "@smithy/types": "^4.12.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.3.tgz", + "integrity": "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg==", + "dependencies": { + "@smithy/core": "^3.23.0", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "dependencies": { + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.30", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.30.tgz", + "integrity": "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng==", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.33", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.33.tgz", + "integrity": "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA==", + "dependencies": { + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "dependencies": { + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz", + "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -1940,9 +3666,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dependencies": { "csstype": "^3.2.2" } @@ -1962,16 +3688,16 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -1984,7 +3710,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", + "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -1999,15 +3725,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "engines": { @@ -2023,13 +3749,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", "dev": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "engines": { @@ -2044,13 +3770,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2061,9 +3787,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2077,14 +3803,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -2101,9 +3827,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2114,15 +3840,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", "dev": true, "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -2177,15 +3903,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2200,12 +3926,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2752,6 +4478,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-cli": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/aws-cli/-/aws-cli-0.0.2.tgz", + "integrity": "sha512-340A6bn3I5p/yc8MGQiWRofW3oUpmZ/k6LIZticzDOKWT4/F+TP54hki9SWmSS6XnvNIVAw4k9oPocq6ya4L1g==", + "deprecated": "Recommend using the official aws cli tools for python", + "dependencies": { + "aws-sdk": "~2.0.0-rc13", + "operetta": "0.0.5" + }, + "bin": { + "aws-cli": "bin/aws.js" + } + }, + "node_modules/aws-sdk": { + "version": "2.0.31", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.0.31.tgz", + "integrity": "sha512-rgmExM3/LjhI95PfjwDs1hks/RYrQ/V3qLybFCzBqr8Y3Hz9Qq3jp+WR1BaWhUckgoVJs/ZE4VSvMKZeNwuFoA==", + "deprecated": "The AWS SDK for JavaScript (v2) has reached end-of-support, and no longer receives updates. Please migrate your code to use AWS SDK for JavaScript (v3). More info https://a.co/cUPnyil", + "dependencies": { + "xml2js": "0.2.6", + "xmlbuilder": "0.4.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -2809,6 +4561,11 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3416,9 +5173,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", - "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "engines": { "node": ">=12" }, @@ -3655,6 +5412,47 @@ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==" }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4156,6 +5954,23 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -4255,11 +6070,11 @@ } }, "node_modules/framer-motion": { - "version": "12.33.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.33.0.tgz", - "integrity": "sha512-ca8d+rRPcDP5iIF+MoT3WNc0KHJMjIyFAbtVLvM9eA7joGSpeqDfiNH/kCs1t4CHi04njYvWyj0jS4QlEK/rJQ==", + "version": "12.34.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.0.tgz", + "integrity": "sha512-+/H49owhzkzQyxtn7nZeF4kdH++I2FWrESQ184Zbcw5cEqNHYkE5yxWxcTLSj5lNx3NWdbIRy5FHqUvetD8FWg==", "dependencies": { - "motion-dom": "^12.33.0", + "motion-dom": "^12.34.0", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, @@ -4280,6 +6095,20 @@ } } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5649,9 +7478,9 @@ } }, "node_modules/motion-dom": { - "version": "12.33.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.33.0.tgz", - "integrity": "sha512-XRPebVypsl0UM+7v0Hr8o9UAj0S2djsQWRdHBd5iVouVpMrQqAI0C/rDAT3QaYnXnHuC5hMcwDHCboNeyYjPoQ==", + "version": "12.34.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.0.tgz", + "integrity": "sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==", "dependencies": { "motion-utils": "^12.29.2" } @@ -5904,9 +7733,9 @@ } }, "node_modules/nypm/node_modules/citty": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz", - "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", + "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==" }, "node_modules/oauth4webapi": { "version": "3.8.4", @@ -6036,6 +7865,14 @@ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==" }, + "node_modules/operetta": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/operetta/-/operetta-0.0.5.tgz", + "integrity": "sha512-b9E0Z5nvkHf9IQ0q7VaWpDfj/nX/pRAcMBoGh4SLrGhC2gE3BD6GwLc2qp64AsZgEwBe2gqIgBc5fqu/WDL9uQ==", + "engines": [ + "node >= 0.4.6" + ] + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6054,16 +7891,16 @@ } }, "node_modules/otplib": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.2.1.tgz", - "integrity": "sha512-Cft9h/m34LtvnoB2TjP1E1E6v0biwcUntl6U4e+HgWrTa0bpwmb+u/D9gLFA+U6/ztlvrult0811Bu30nUVUuA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.3.0.tgz", + "integrity": "sha512-VYMKyyDG8yt2q+z58sz54/EIyTh7+tyMrjeemR44iVh5+dkKtIs57irTqxjH+IkAL1uMmG1JIFhG5CxTpqdU5g==", "dependencies": { - "@otplib/core": "13.2.1", - "@otplib/hotp": "13.2.1", - "@otplib/plugin-base32-scure": "13.2.1", - "@otplib/plugin-crypto-noble": "13.2.1", - "@otplib/totp": "13.2.1", - "@otplib/uri": "13.2.1" + "@otplib/core": "13.3.0", + "@otplib/hotp": "13.3.0", + "@otplib/plugin-base32-scure": "13.3.0", + "@otplib/plugin-crypto-noble": "13.3.0", + "@otplib/totp": "13.3.0", + "@otplib/uri": "13.3.0" } }, "node_modules/own-keys": { @@ -6399,14 +8236,14 @@ } }, "node_modules/prisma": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.3.0.tgz", - "integrity": "sha512-ApYSOLHfMN8WftJA+vL6XwAPOh/aZ0BgUyyKPwUFgjARmG6EBI9LzDPf6SWULQMSAxydV9qn5gLj037nPNlg2w==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.4.0.tgz", + "integrity": "sha512-n2xU9vSaH4uxZF/l2aKoGYtKtC7BL936jM9Q94Syk1zOD39t/5hjDUxMgaPkVRDX5wWEMsIqvzQxoebNIesOKw==", "hasInstallScript": true, "dependencies": { - "@prisma/config": "7.3.0", + "@prisma/config": "7.4.0", "@prisma/dev": "0.20.0", - "@prisma/engines": "7.3.0", + "@prisma/engines": "7.4.0", "@prisma/studio-core": "0.13.1", "mysql2": "3.15.3", "postgres": "3.4.7" @@ -6441,6 +8278,12 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -6561,9 +8404,10 @@ } }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "peer": true }, "node_modules/react-redux": { "version": "9.2.0", @@ -6847,6 +8691,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.4.2.tgz", + "integrity": "sha512-6Zsl4gnHjiTQfrOzsWdc0bHJepF5KJAVuM5fcyEJrqGyszkx2c55IclWP4D692rJrl1w0tExhbvYKjKNZl5wHg==" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -7280,6 +9129,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -7463,6 +9323,25 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7563,15 +9442,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7854,6 +9733,22 @@ "node": ">=8" } }, + "node_modules/xml2js": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.6.tgz", + "integrity": "sha512-h+Nzgo0eLwideldZGqSquNtrsuX5zcDREImBa1GSNw9QljlXiFcM3E1kWjLprRjJe2jRuvnVk0j3WgQo9Deoog==", + "dependencies": { + "sax": "0.4.2" + } + }, + "node_modules/xmlbuilder": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.2.tgz", + "integrity": "sha512-h/+ncQQSU/iYycmI2wTN25t7RYN7O2oq9uvI+2+UObi4KcmQh/jUS4N31g5vJttQt7MODsnmBtbcll3YbNyvfw==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 8ea9714..775a248 100644 --- a/package.json +++ b/package.json @@ -6,17 +6,21 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "re-embed-knowledge-base": "tsx scripts/re-embed-knowledge-base.ts" }, "dependencies": { "@auth/core": "^0.41.0", "@auth/prisma-adapter": "^2.11.1", + "@aws-sdk/client-bedrock-runtime": "^3.989.0", "@prisma/adapter-pg": "^7.3.0", "@prisma/client": "^7.3.0", + "@tailwindcss/postcss": "^4", "@types/nodemailer": "^7.0.9", "@types/qrcode": "^1.5.6", "@upstash/ratelimit": "^2.0.5", "@upstash/redis": "^1.34.3", + "aws-cli": "^0.0.2", "bcryptjs": "^3.0.3", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -35,8 +39,7 @@ "react-hot-toast": "^2.6.0", "recharts": "^3.7.0", "tailwind-merge": "^3.4.0", - "zod": "^3.23.8", - "@tailwindcss/postcss": "^4" + "zod": "^3.23.8" }, "devDependencies": { "@types/bcryptjs": "^3.0.0", @@ -48,6 +51,7 @@ "eslint": "^9", "eslint-config-next": "16.1.6", "tailwindcss": "^4", + "tsx": "^4.21.0", "typescript": "^5" } } diff --git a/prisma/migrations/20260209_init_pgvector/steps.sql b/prisma/migrations/20260209_init_pgvector/migration.sql similarity index 100% rename from prisma/migrations/20260209_init_pgvector/steps.sql rename to prisma/migrations/20260209_init_pgvector/migration.sql diff --git a/prisma/migrations/20260213120753_bedrock_embeddings/migration.sql b/prisma/migrations/20260213120753_bedrock_embeddings/migration.sql new file mode 100644 index 0000000..e15da5a --- /dev/null +++ b/prisma/migrations/20260213120753_bedrock_embeddings/migration.sql @@ -0,0 +1,22 @@ +-- Migrate KnowledgeChunk embeddings from 1536 dimensions (Gemini) to 1024 dimensions (Bedrock Titan V2) +-- +-- NOTE: This migration changes the vector dimension. Existing embeddings will need to be +-- re-embedded using the re-embedding script after this migration runs. +-- +-- Strategy: Drop the index, change column type (will truncate/pad existing vectors), +-- then recreate the index with the new dimension. + +-- Drop the existing index +DROP INDEX IF EXISTS "KnowledgeChunk_embedding_storeId_idx"; + +-- Change the embedding column dimension from 1536 to 1024 +-- PostgreSQL will automatically truncate vectors longer than 1024 or pad shorter ones +ALTER TABLE "KnowledgeChunk" + ALTER COLUMN "embedding" TYPE vector(1024); + +-- Recreate the index with the new dimension +CREATE INDEX IF NOT EXISTS "KnowledgeChunk_embedding_storeId_idx" + ON "KnowledgeChunk" + USING ivfflat ("embedding" vector_cosine_ops) + WITH (lists = 100) + WHERE "storeId" IS NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1a8a46c..2acd71c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -109,6 +109,7 @@ model Store { faqItems FaqItem[] knowledgeChunks KnowledgeChunk[] emailLogs EmailLog[] + templates Template[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -461,3 +462,23 @@ model KnowledgeChunk { @@index([documentId]) @@index([faqItemId]) } + +model Template { + id String @id @default(cuid()) + storeId String + store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) + + name String + intent String + mood String + description String? + + body String // Template body (HTML or plain text with {{variables}}) + variables Json? // Optional metadata describing expected variables + isHtml Boolean @default(true) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([storeId]) +} diff --git a/scripts/re-embed-knowledge-base.ts b/scripts/re-embed-knowledge-base.ts new file mode 100644 index 0000000..5039af7 --- /dev/null +++ b/scripts/re-embed-knowledge-base.ts @@ -0,0 +1,156 @@ +#!/usr/bin/env tsx +/** + * Re-embedding script for Knowledge Base + * + * This script re-embeds all existing KnowledgeChunk records using AWS Bedrock + * Titan Text Embeddings V2 (1024 dimensions) to replace Gemini embeddings (1536 dimensions). + * + * Usage: + * npm run re-embed-knowledge-base + * or + * tsx scripts/re-embed-knowledge-base.ts + */ + +import { prisma } from "../src/lib/prisma" +import { embedBatch } from "../src/lib/kb/bedrockEmbeddings" +import { logger } from "../src/lib/logger" + +// AWS Bedrock Titan V2 limit: 2000 requests/minute +// Use smaller batches with delays to stay under limit +const BATCH_SIZE = 10 // Process chunks in smaller batches to avoid rate limits + +interface KnowledgeChunk { + id: string + content: string + storeId: string +} + +async function reEmbedKnowledgeBase() { + try { + logger.info("Starting knowledge base re-embedding process...") + + // Fetch all knowledge chunks + const sql = `SELECT "id", "content", "storeId" + FROM "KnowledgeChunk" + WHERE "content" IS NOT NULL AND "content" != '' + ORDER BY "createdAt" ASC` + const chunks = await prisma.$queryRawUnsafe(sql) + + if (chunks.length === 0) { + logger.info("No knowledge chunks found to re-embed.") + return + } + + logger.info(`Found ${chunks.length} knowledge chunks to re-embed.`) + + let processed = 0 + let failed = 0 + const totalBatches = Math.ceil(chunks.length / BATCH_SIZE) + + // Process in batches + for (let i = 0; i < chunks.length; i += BATCH_SIZE) { + const batch = chunks.slice(i, i + BATCH_SIZE) + const batchNumber = Math.floor(i / BATCH_SIZE) + 1 + + logger.info( + `Processing batch ${batchNumber}/${totalBatches} (${batch.length} chunks)...`, + ) + + try { + // Generate embeddings for this batch + const texts = batch.map((chunk) => chunk.content) + const embeddings = await embedBatch(texts) + + if (embeddings.length !== batch.length) { + logger.error( + `Embedding count mismatch: expected ${batch.length}, got ${embeddings.length}`, + ) + failed += batch.length + continue + } + + // Update each chunk with its new embedding + const updatePromises = batch.map(async (chunk, index) => { + const embedding = embeddings[index] + + if (!embedding || embedding.length === 0) { + logger.warn( + `Skipping chunk ${chunk.id}: empty embedding generated`, + ) + failed++ + return + } + + if (embedding.length !== 1024) { + logger.warn( + `Chunk ${chunk.id}: embedding dimension is ${embedding.length}, expected 1024`, + ) + // Pad or truncate to 1024 + if (embedding.length < 1024) { + embedding.push(...new Array(1024 - embedding.length).fill(0)) + } else { + embedding.splice(1024) + } + } + + try { + await prisma.$executeRawUnsafe( + ` + UPDATE "KnowledgeChunk" + SET "embedding" = $1::vector(1024), + "updatedAt" = NOW() + WHERE "id" = $2 + `, + `[${embedding.join(",")}]`, + chunk.id, + ) + processed++ + } catch (error) { + logger.error(`Failed to update chunk ${chunk.id}:`, error) + failed++ + } + }) + + await Promise.all(updatePromises) + + logger.info( + `Batch ${batchNumber} completed. Processed: ${processed}, Failed: ${failed}`, + ) + + // Add delay between batches to avoid rate limiting + // With 2s delay per request + batch processing, we need extra time between batches + if (i + BATCH_SIZE < chunks.length) { + // Wait 5 seconds between batches to ensure we stay under 2000 req/min limit + await new Promise((resolve) => setTimeout(resolve, 5000)) + } + } catch (error) { + logger.error(`Error processing batch ${batchNumber}:`, error) + failed += batch.length + } + } + + logger.info( + `Re-embedding completed. Processed: ${processed}, Failed: ${failed}, Total: ${chunks.length}`, + ) + } catch (error) { + logger.error("Fatal error during re-embedding:", error) + process.exit(1) + } finally { + await prisma.$disconnect() + } +} + +// Run the script +if (require.main === module) { + reEmbedKnowledgeBase() + .then(() => { + logger.info("Re-embedding script finished successfully.") + process.exit(0) + }) + .catch((error) => { + logger.error("Re-embedding script failed:", error) + process.exit(1) + }) +} + +export { reEmbedKnowledgeBase } diff --git a/scripts/seed-templates.ts b/scripts/seed-templates.ts new file mode 100644 index 0000000..49c3e83 --- /dev/null +++ b/scripts/seed-templates.ts @@ -0,0 +1,291 @@ +#!/usr/bin/env tsx +/** + * Seed script for predefined JUNOHUB email templates. + * + * Usage: + * tsx scripts/seed-templates.ts + * + * Behaviour: + * - Finds a target store: + * - If SEED_STORE_ID is set, uses that storeId + * - Otherwise, uses the first active store it can find + * - Inserts a small library of templates for common intents/moods + * - Idempotent: skips creating a template if one with the same + * (storeId, intent, mood, name) already exists. + */ + +import { prisma } from "../src/lib/prisma" +import { logger } from "../src/lib/logger" +import { randomUUID } from "crypto" + +type SeedTemplate = { + name: string + intent: string + mood: string + description: string + body: string +} + +const SEED_TEMPLATES: SeedTemplate[] = [ + // Shipping delay - angry customer + { + name: "Shipping delay – angry customer", + intent: "shipping_delay", + mood: "angry", + description: + "Apology and clear acknowledgement for an angry customer asking where their order is.", + body: `Hi {{customer_name}}, + +I'm really sorry for the frustration here. I can see why you'd be upset, and I appreciate you taking the time to reach out. + +We've reviewed your message and we're looking into the status of your order right away. {{ticket_summary}} + +As soon as we have an updated timeline from our shipping partner, we'll follow up with you by email. If you have any extra details you think might help, you can reply directly to this message. + +Thank you for your patience while we sort this out. + +Best regards, +{{store_name}} Support Team`, + }, + // Shipping delay - confused / neutral customer + { + name: "Shipping delay – confused customer", + intent: "shipping_delay", + mood: "confused", + description: + "Reassuring response for a customer who is unsure where their order is.", + body: `Hi {{customer_name}}, + +Thanks for reaching out about your order – I’m glad you checked in. + +We’ve received your message and we’re reviewing your order details now. {{ticket_summary}} + +We’re confirming the latest tracking information with our shipping partner and will follow up with an update as soon as we have it. In the meantime, if you notice any changes or receive a new tracking email, feel free to reply here so we can keep everything in one place. + +Best regards, +{{store_name}} Support Team`, + }, + // Refund / return request – neutral + { + name: "Refund or return – neutral", + intent: "refund_request", + mood: "neutral", + description: + "Professional acknowledgement of a refund/return request with next steps.", + body: `Hi {{customer_name}}, + +Thank you for contacting us about your refund/return request. + +We’ve reviewed your message and we’re now looking into the order details so we can process this correctly. {{ticket_summary}} + +In some cases we may need a bit more information (for example photos of the item or confirmation of the order number). If that’s needed, we’ll reply back with a short checklist so you know exactly what to send. + +Once everything is confirmed, we’ll process your refund/return and email you a final confirmation. + +Best regards, +{{store_name}} Support Team`, + }, + // Refund / return request – appreciative / happy + { + name: "Refund or return – polite customer", + intent: "refund_request", + mood: "happy", + description: + "Warm, appreciative reply when the customer is polite and reasonable about a refund/return.", + body: `Hi {{customer_name}}, + +Thank you for your thoughtful message – we really appreciate how clearly you explained the situation. + +We’ve started reviewing your request and the details of your order. {{ticket_summary}} + +If we need anything else from you (for example photos or a bit more context), we’ll let you know right away. Otherwise, we’ll process your refund/return and follow up with a confirmation email as soon as it’s completed. + +Thank you again for shopping with {{store_name}}. + +Warm regards, +{{store_name}} Support Team`, + }, + // Product question / general support – neutral + { + name: "Product question – we’re checking", + intent: "question", + mood: "neutral", + description: + "Neutral response letting the customer know their product question is being reviewed.", + body: `Hi {{customer_name}}, + +Thanks for reaching out with your question – we’re glad you asked. + +We’ve received your message and we’re checking the details so we can give you a clear answer. {{ticket_summary}} + +As soon as we’ve confirmed everything with the relevant team, we’ll follow up with a detailed reply. If you have any extra information or screenshots that might help, you can reply to this email and they’ll be added to your ticket. + +Best regards, +{{store_name}} Support Team`, + }, + // Product question – confused customer + { + name: "Product question – confused customer", + intent: "question", + mood: "confused", + description: + "Reassuring answer for a customer who is confused about how something works.", + body: `Hi {{customer_name}}, + +Thank you for reaching out and for explaining what’s been confusing – you’re not alone, and we’re here to help. + +We’ve received your message and we’re reviewing the details so we can walk you through this step by step. {{ticket_summary}} + +Once we’ve double-checked everything, we’ll follow up with a clear explanation and, if helpful, some simple steps you can follow. If you have any screenshots or additional details, feel free to reply to this email so we can see exactly what you’re seeing. + +Warm regards, +{{store_name}} Support Team`, + }, + // Account / billing issue – frustrated customer + { + name: "Account or billing – concerned customer", + intent: "account_billing", + mood: "angry", + description: + "Empathetic response when a customer is upset about a billing or account issue.", + body: `Hi {{customer_name}}, + +I’m really sorry for the worry and frustration this billing issue has caused – I completely understand how important this is. + +We’ve received your message and we’re reviewing your account and recent charges as a priority. {{ticket_summary}} + +If we need any additional details to resolve this, we’ll let you know exactly what we need and why. Otherwise, we’ll follow up as soon as we have a full explanation and next steps for you. + +Thank you for your patience while we get to the bottom of this. + +Best regards, +{{store_name}} Support Team`, + }, + // Account / billing issue – neutral + { + name: "Account or billing – investigation", + intent: "account_billing", + mood: "neutral", + description: + "Calm, neutral response acknowledging an account or billing question and starting an investigation.", + body: `Hi {{customer_name}}, + +Thanks for getting in touch about your account/billing question. + +We’ve received your message and we’re reviewing your account details and recent activity. {{ticket_summary}} + +If we need any additional information from you, we’ll reply with a short list so it’s easy to provide. Once we’ve completed our review, we’ll follow up with a clear explanation of what we found and, if needed, any adjustments we’ll make. + +Best regards, +{{store_name}} Support Team`, + }, + // Thank-you / positive feedback – happy customer + { + name: "Thank-you – happy customer", + intent: "general", + mood: "happy", + description: + "Warm reply to a customer who sends positive feedback or a thank-you message.", + body: `Hi {{customer_name}}, + +Thank you so much for your kind message – it truly made our day here at {{store_name}}. + +We really appreciate you taking the time to share your experience. {{ticket_summary}} + +If there’s ever anything else we can help with, just reply to this email and we’ll be glad to assist. + +Warm regards, +{{store_name}} Support Team`, + }, +] + +async function getTargetStoreId(): Promise { + const explicitId = process.env.SEED_STORE_ID + if (explicitId) { + const store = await prisma.store.findUnique({ + where: { id: explicitId }, + select: { id: true }, + }) + if (!store) { + throw new Error(`SEED_STORE_ID is set, but no store found with id=${explicitId}`) + } + return store.id + } + + const store = await prisma.store.findFirst({ + where: { isActive: true }, + select: { id: true }, + }) + + if (!store) { + throw new Error( + "No active store found. Set SEED_STORE_ID to a valid store id before running this script.", + ) + } + + return store.id +} + +async function seedTemplates() { + try { + logger.info("Starting template seeding...") + + const storeId = await getTargetStoreId() + logger.info(`Seeding templates for storeId=${storeId}`) + + for (const tpl of SEED_TEMPLATES) { + // Check if a template with the same (storeId, intent, mood, name) already exists + const existing = await prisma.$queryRaw< + { id: string }[] + >` + SELECT "id" + FROM "Template" + WHERE "storeId" = ${storeId} + AND "intent" = ${tpl.intent} + AND "mood" = ${tpl.mood} + AND "name" = ${tpl.name} + LIMIT 1 + ` + + if (existing.length > 0) { + logger.info( + `Skipping existing template: "${tpl.name}" (intent=${tpl.intent}, mood=${tpl.mood})`, + ) + continue + } + + const id = randomUUID() + + await prisma.$executeRaw` + INSERT INTO "Template" + ("id", "storeId", "name", "intent", "mood", "description", "body", "variables", "isHtml", "createdAt", "updatedAt") + VALUES + (${id}, ${storeId}, ${tpl.name}, ${tpl.intent}, ${tpl.mood}, ${tpl.description}, ${tpl.body}, NULL, FALSE, NOW(), NOW()) + ` + + logger.info( + `Inserted template: "${tpl.name}" (intent=${tpl.intent}, mood=${tpl.mood})`, + ) + } + + logger.info("Template seeding completed.") + } catch (error) { + logger.error("Template seeding failed:", error) + process.exit(1) + } finally { + await prisma.$disconnect() + } +} + +if (require.main === module) { + seedTemplates() + .then(() => { + logger.info("Seed script finished successfully.") + process.exit(0) + }) + .catch((error) => { + logger.error("Seed script encountered an error:", error) + process.exit(1) + }) +} + diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts index d4c4187..fa9ca36 100644 --- a/src/app/api/health/route.ts +++ b/src/app/api/health/route.ts @@ -1,8 +1,12 @@ import { NextResponse } from "next/server" import { prisma } from "@/lib/prisma" +import { startGmailPoller } from "@/lib/gmail/poller" export async function GET() { try { + // Start the background Gmail poller (idempotent - safe to call repeatedly) + startGmailPoller() + // Simple database connectivity check await prisma.$queryRaw`SELECT 1` diff --git a/src/app/api/integrations/gmail/callback/route.ts b/src/app/api/integrations/gmail/callback/route.ts index 7755311..dc1450a 100644 --- a/src/app/api/integrations/gmail/callback/route.ts +++ b/src/app/api/integrations/gmail/callback/route.ts @@ -10,8 +10,13 @@ export async function GET(req: Request) { try { const session = await auth() if (!session?.user?.id) { + const baseUrl = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( - new URL("/login?error=unauthorized", process.env.NEXTAUTH_URL || "http://localhost:3000") + new URL("/login?error=unauthorized", baseUrl) ) } @@ -23,20 +28,34 @@ export async function GET(req: Request) { // Handle OAuth errors if (error) { console.error("Gmail OAuth error:", error) + const baseUrl = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( - new URL("/dashboard/juno-engine/gmail-connect?error=oauth_failed", process.env.NEXTAUTH_URL || "http://localhost:3000") + new URL("/dashboard/juno-engine/gmail-connect?error=oauth_failed", baseUrl) ) } if (!code) { + const baseUrl = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( - new URL("/dashboard/juno-engine/gmail-connect?error=no_code", process.env.NEXTAUTH_URL || "http://localhost:3000") + new URL("/dashboard/juno-engine/gmail-connect?error=no_code", baseUrl) ) } // Exchange authorization code for tokens // IMPORTANT: This redirect URI must exactly match what was sent in the authorization request - const baseUrl = process.env.NEXTAUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000" + const baseUrl = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" const redirectUri = `${baseUrl}/api/integrations/gmail/callback` console.log("Gmail OAuth Callback - Redirect URI:", redirectUri) // Log for debugging @@ -62,16 +81,26 @@ export async function GET(req: Request) { // Provide more specific error message if it's a redirect_uri_mismatch if (errorData.error === "redirect_uri_mismatch") { + const baseUrlForError = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( new URL( `/dashboard/juno-engine/gmail-connect?error=redirect_uri_mismatch&redirect_uri=${encodeURIComponent(redirectUri)}`, - process.env.NEXTAUTH_URL || "http://localhost:3000" + baseUrlForError ) ) } + const baseUrlForError = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( - new URL("/dashboard/juno-engine/gmail-connect?error=token_exchange_failed", process.env.NEXTAUTH_URL || "http://localhost:3000") + new URL("/dashboard/juno-engine/gmail-connect?error=token_exchange_failed", baseUrlForError) ) } @@ -113,15 +142,25 @@ export async function GET(req: Request) { } } catch (error) { console.error("Error fetching user info:", error) + const baseUrlForError = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( - new URL("/dashboard/juno-engine/gmail-connect?error=user_info_failed", process.env.NEXTAUTH_URL || "http://localhost:3000") + new URL("/dashboard/juno-engine/gmail-connect?error=user_info_failed", baseUrlForError) ) } if (!userInfo?.email) { console.error("No email found in user info") + const baseUrlForError = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( - new URL("/dashboard/juno-engine/gmail-connect?error=user_info_failed", process.env.NEXTAUTH_URL || "http://localhost:3000") + new URL("/dashboard/juno-engine/gmail-connect?error=user_info_failed", baseUrlForError) ) } @@ -151,13 +190,23 @@ export async function GET(req: Request) { } }) + const baseUrlDone = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( - new URL("/dashboard/juno-engine/gmail-connect?connected=true", process.env.NEXTAUTH_URL || "http://localhost:3000") + new URL("/dashboard/juno-engine/gmail-connect?connected=true", baseUrlDone) ) } catch (error) { console.error("Gmail callback error:", error) + const baseUrlError = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" return NextResponse.redirect( - new URL("/dashboard/juno-engine/gmail-connect?error=callback_failed", process.env.NEXTAUTH_URL || "http://localhost:3000") + new URL("/dashboard/juno-engine/gmail-connect?error=callback_failed", baseUrlError) ) } } diff --git a/src/app/api/integrations/gmail/connect/route.ts b/src/app/api/integrations/gmail/connect/route.ts index 2bb140e..2d2d7da 100644 --- a/src/app/api/integrations/gmail/connect/route.ts +++ b/src/app/api/integrations/gmail/connect/route.ts @@ -38,7 +38,11 @@ export async function POST() { // For production: https://yourdomain.com/api/integrations/gmail/callback // For development: http://localhost:3000/api/integrations/gmail/callback // For ngrok: https://your-ngrok-url.ngrok-free.dev/api/integrations/gmail/callback - const baseUrl = process.env.NEXTAUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000" + const baseUrl = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" const redirectUri = `${baseUrl}/api/integrations/gmail/callback` console.log("Gmail OAuth - Redirect URI:", redirectUri) // Log for debugging diff --git a/src/app/api/integrations/gmail/watch/route.ts b/src/app/api/integrations/gmail/watch/route.ts new file mode 100644 index 0000000..2497bbb --- /dev/null +++ b/src/app/api/integrations/gmail/watch/route.ts @@ -0,0 +1,158 @@ +import { NextResponse } from "next/server" +import { auth } from "@/auth" +import { prisma } from "@/lib/prisma" +import { logger } from "@/lib/logger" +import { getGmailAccessToken } from "@/lib/gmail" + +/** + * POST /api/integrations/gmail/watch + * Set up Gmail push notifications (watch) for a user's mailbox + * + * This creates a watch request that tells Gmail to send push notifications + * to the webhook endpoint when new emails arrive. + * + * Note: Gmail watch requests expire after 7 days and need to be renewed. + */ +export async function POST(req: Request) { + try { + const session = await auth() + if (!session?.user?.id) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }) + } + + // Get Gmail access token + const accessToken = await getGmailAccessToken(session.user.id) + if (!accessToken) { + return NextResponse.json( + { message: "Gmail not connected. Please connect your Gmail account first." }, + { status: 400 } + ) + } + + // Get webhook URL + const baseUrl = + process.env.AUTH_URL || + process.env.NEXTAUTH_URL || + process.env.NEXT_PUBLIC_APP_URL || + "http://localhost:3000" + const webhookUrl = `${baseUrl}/api/webhooks/gmail` + + // Get user's email from integration + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const integration = await (prisma as any).gmailIntegration.findUnique({ + where: { userId: session.user.id }, + select: { email: true } + }) + + if (!integration?.email) { + return NextResponse.json( + { message: "Gmail email not found" }, + { status: 400 } + ) + } + + // Create watch request + // Gmail will send push notifications to the webhook URL + const watchResponse = await fetch( + "https://gmail.googleapis.com/gmail/v1/users/me/watch", + { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + topicName: process.env.GMAIL_PUBSUB_TOPIC || "projects/your-project/topics/gmail-notifications", + labelIds: ["INBOX"], // Watch for emails in inbox + }) + } + ) + + if (!watchResponse.ok) { + const errorData = await watchResponse.json().catch(() => ({})) + logger.error("Failed to set up Gmail watch", { error: errorData }) + + // If topic doesn't exist, provide helpful error message + if (watchResponse.status === 400) { + return NextResponse.json( + { + message: "Gmail Pub/Sub topic not configured. Please set up Google Cloud Pub/Sub first.", + error: errorData + }, + { status: 400 } + ) + } + + return NextResponse.json( + { message: "Failed to set up Gmail watch", error: errorData }, + { status: watchResponse.status } + ) + } + + const watchData = await watchResponse.json() + const expiration = watchData.expiration ? new Date(parseInt(watchData.expiration)).toISOString() : null + + logger.info("Gmail watch set up successfully", { + userId: session.user.id, + email: integration.email, + expiration + }) + + return NextResponse.json({ + success: true, + message: "Gmail push notifications enabled", + expiration, + webhookUrl + }) + } catch (error) { + logger.error("Error setting up Gmail watch", error) + return NextResponse.json( + { message: "Failed to set up Gmail watch" }, + { status: 500 } + ) + } +} + +/** + * GET /api/integrations/gmail/watch + * Check watch status + */ +export async function GET() { + try { + const session = await auth() + if (!session?.user?.id) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }) + } + + const accessToken = await getGmailAccessToken(session.user.id) + if (!accessToken) { + return NextResponse.json( + { message: "Gmail not connected" }, + { status: 400 } + ) + } + + // Stop any existing watch (optional - just to check status) + const stopResponse = await fetch( + "https://gmail.googleapis.com/gmail/v1/users/me/stop", + { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json" + } + } + ) + + return NextResponse.json({ + watchActive: !stopResponse.ok, // If stop fails, watch might be active + message: stopResponse.ok ? "Watch stopped" : "Watch may be active" + }) + } catch (error) { + logger.error("Error checking Gmail watch status", error) + return NextResponse.json( + { message: "Failed to check watch status" }, + { status: 500 } + ) + } +} diff --git a/src/app/api/juno-engine/templates/[templateId]/route.ts b/src/app/api/juno-engine/templates/[templateId]/route.ts new file mode 100644 index 0000000..c13d16e --- /dev/null +++ b/src/app/api/juno-engine/templates/[templateId]/route.ts @@ -0,0 +1,246 @@ +import { NextResponse } from "next/server" +import { auth } from "@/auth" +import { prisma } from "@/lib/prisma" +import { logger } from "@/lib/logger" + +type TemplateRow = { + id: string + storeId: string + name: string + intent: string + mood: string + description: string | null + body: string + variables: unknown | null + isHtml: boolean + updatedAt: Date +} + +async function getActiveStoreId(userId: string) { + const user = await prisma.user.findUnique({ + where: { id: userId }, + include: { + stores: { + where: { isActive: true }, + take: 1, + select: { id: true }, + }, + }, + }) + + return user?.stores[0]?.id ?? null +} + +export async function GET( + _req: Request, + { params }: { params: Promise<{ templateId: string }> }, +) { + try { + const session = await auth() + if (!session?.user?.id) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }) + } + + if (session.user.role !== "VENDOR") { + return NextResponse.json({ message: "Forbidden" }, { status: 403 }) + } + + const { templateId } = await params + + const storeId = await getActiveStoreId(session.user.id) + if (!storeId) { + return NextResponse.json( + { message: "No active store found" }, + { status: 400 }, + ) + } + + const rows = await prisma.$queryRaw` + SELECT + "id", + "storeId", + "name", + "intent", + "mood", + "description", + "body", + "variables", + "isHtml", + "updatedAt" + FROM "Template" + WHERE "id" = ${templateId} AND "storeId" = ${storeId} + LIMIT 1 + ` + + const row = rows[0] + + if (!row) { + return NextResponse.json({ message: "Template not found" }, { status: 404 }) + } + + const template = { + ...row, + variables: row.variables ?? undefined, + updatedAt: row.updatedAt.toISOString(), + } + + return NextResponse.json({ template }) + } catch (error) { + logger.error("Failed to load template", error) + return NextResponse.json( + { message: "Failed to load template" }, + { status: 500 }, + ) + } +} + +export async function PUT( + req: Request, + { params }: { params: Promise<{ templateId: string }> }, +) { + try { + const session = await auth() + if (!session?.user?.id) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }) + } + + if (session.user.role !== "VENDOR") { + return NextResponse.json({ message: "Forbidden" }, { status: 403 }) + } + + const { templateId } = await params + + const storeId = await getActiveStoreId(session.user.id) + if (!storeId) { + return NextResponse.json( + { message: "No active store found" }, + { status: 400 }, + ) + } + + const existingRows = await prisma.$queryRaw[]>` + SELECT "id" + FROM "Template" + WHERE "id" = ${templateId} AND "storeId" = ${storeId} + LIMIT 1 + ` + const existing = existingRows[0] + if (!existing) { + return NextResponse.json({ message: "Template not found" }, { status: 404 }) + } + + const body = await req.json().catch(() => null) + if (!body) { + return NextResponse.json({ message: "Invalid JSON body" }, { status: 400 }) + } + + const { + name, + intent, + mood, + description, + body: templateBody, + variables, + isHtml, + } = body as { + name?: string + intent?: string + mood?: string + description?: string | null + body?: string + variables?: unknown + isHtml?: boolean + } + + const rows = await prisma.$queryRaw` + UPDATE "Template" + SET + "name" = COALESCE(${name}, "name"), + "intent" = COALESCE(${intent}, "intent"), + "mood" = COALESCE(${mood}, "mood"), + "description" = COALESCE(${description ?? null}, "description"), + "body" = COALESCE(${templateBody}, "body"), + "variables" = COALESCE(${variables as any}, "variables"), + "isHtml" = COALESCE(${isHtml}, "isHtml"), + "updatedAt" = NOW() + WHERE "id" = ${existing.id} + RETURNING + "id", + "storeId", + "name", + "intent", + "mood", + "description", + "body", + "variables", + "isHtml", + "updatedAt" + ` + + const row = rows[0] + const updated = { + ...row, + variables: row.variables ?? undefined, + updatedAt: row.updatedAt.toISOString(), + } + + return NextResponse.json({ template: updated }) + } catch (error) { + logger.error("Failed to update template", error) + return NextResponse.json( + { message: "Failed to update template" }, + { status: 500 }, + ) + } +} + +export async function DELETE( + _req: Request, + { params }: { params: Promise<{ templateId: string }> }, +) { + try { + const session = await auth() + if (!session?.user?.id) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }) + } + + if (session.user.role !== "VENDOR") { + return NextResponse.json({ message: "Forbidden" }, { status: 403 }) + } + + const { templateId } = await params + + const storeId = await getActiveStoreId(session.user.id) + if (!storeId) { + return NextResponse.json( + { message: "No active store found" }, + { status: 400 }, + ) + } + + const existingRows = await prisma.$queryRaw[]>` + SELECT "id" + FROM "Template" + WHERE "id" = ${templateId} AND "storeId" = ${storeId} + LIMIT 1 + ` + const existing = existingRows[0] + if (!existing) { + return NextResponse.json({ message: "Template not found" }, { status: 404 }) + } + + await prisma.$executeRaw` + DELETE FROM "Template" + WHERE "id" = ${existing.id} + ` + + return NextResponse.json({ success: true }) + } catch (error) { + logger.error("Failed to delete template", error) + return NextResponse.json( + { message: "Failed to delete template" }, + { status: 500 }, + ) + } +} + diff --git a/src/app/api/juno-engine/templates/route.ts b/src/app/api/juno-engine/templates/route.ts new file mode 100644 index 0000000..6e20618 --- /dev/null +++ b/src/app/api/juno-engine/templates/route.ts @@ -0,0 +1,184 @@ +import { NextResponse } from "next/server" +import { auth } from "@/auth" +import { prisma } from "@/lib/prisma" +import { logger } from "@/lib/logger" +import { randomUUID } from "crypto" + +type TemplateRow = { + id: string + storeId: string + name: string + intent: string + mood: string + description: string | null + body: string + variables: unknown | null + isHtml: boolean + updatedAt: Date +} + +export async function GET(req: Request) { + try { + const session = await auth() + if (!session?.user?.id) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }) + } + + if (session.user.role !== "VENDOR") { + return NextResponse.json({ message: "Forbidden" }, { status: 403 }) + } + + const url = new URL(req.url) + const intent = url.searchParams.get("intent") || undefined + const mood = url.searchParams.get("mood") || undefined + + const user = await prisma.user.findUnique({ + where: { id: session.user.id }, + include: { + stores: { + where: { isActive: true }, + take: 1, + select: { id: true }, + }, + }, + }) + + const activeStore = user?.stores[0] + if (!activeStore) { + return NextResponse.json({ templates: [] }) + } + + // Use raw SQL so we don't depend on generated Prisma delegates + const rows = await prisma.$queryRaw` + SELECT + "id", + "storeId", + "name", + "intent", + "mood", + "description", + "body", + "variables", + "isHtml", + "updatedAt" + FROM "Template" + WHERE "storeId" = ${activeStore.id} + ORDER BY "updatedAt" DESC + ` + + const filtered = + intent || mood + ? rows.filter((tpl: TemplateRow) => { + if (intent && tpl.intent !== intent) return false + if (mood && tpl.mood !== mood) return false + return true + }) + : rows + + const templates = filtered.map((tpl: TemplateRow) => ({ + ...tpl, + variables: tpl.variables ?? undefined, + updatedAt: tpl.updatedAt.toISOString(), + })) + + return NextResponse.json({ templates }) + } catch (error) { + logger.error("Failed to load templates", error) + return NextResponse.json( + { message: "Failed to load templates", templates: [] }, + { status: 500 }, + ) + } +} + +export async function POST(req: Request) { + try { + const session = await auth() + if (!session?.user?.id) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }) + } + + if (session.user.role !== "VENDOR") { + return NextResponse.json({ message: "Forbidden" }, { status: 403 }) + } + + const user = await prisma.user.findUnique({ + where: { id: session.user.id }, + include: { + stores: { + where: { isActive: true }, + take: 1, + select: { id: true }, + }, + }, + }) + + const activeStore = user?.stores[0] + if (!activeStore) { + return NextResponse.json( + { message: "No active store found" }, + { status: 400 }, + ) + } + + const body = await req.json().catch(() => null) + if (!body) { + return NextResponse.json({ message: "Invalid JSON body" }, { status: 400 }) + } + + const { name, intent, mood, description, body: templateBody, variables, isHtml } = + body as { + name?: string + intent?: string + mood?: string + description?: string | null + body?: string + variables?: unknown + isHtml?: boolean + } + + if (!name || !intent || !mood || !templateBody) { + return NextResponse.json( + { message: "Missing required fields (name, intent, mood, body)" }, + { status: 400 }, + ) + } + + const id = randomUUID() + + const rows = await prisma.$queryRaw` + INSERT INTO "Template" + ("id", "storeId", "name", "intent", "mood", "description", "body", "variables", "isHtml", "createdAt", "updatedAt") + VALUES + (${id}, ${activeStore.id}, ${name}, ${intent}, ${mood}, ${description ?? null}, ${templateBody}, ${variables as + any}, ${isHtml ?? true}, NOW(), NOW()) + RETURNING + "id", + "storeId", + "name", + "intent", + "mood", + "description", + "body", + "variables", + "isHtml", + "updatedAt" + ` + + const inserted = rows[0] + const template = { + ...inserted, + variables: inserted.variables ?? undefined, + updatedAt: inserted.updatedAt.toISOString(), + } + + return NextResponse.json({ template }, { status: 201 }) + } catch (error) { + logger.error("Failed to create template", error) + return NextResponse.json( + { message: "Failed to create template" }, + { status: 500 }, + ) + } +} + diff --git a/src/app/api/juno-engine/tickets/route.ts b/src/app/api/juno-engine/tickets/route.ts index e6ac3fe..82be510 100644 --- a/src/app/api/juno-engine/tickets/route.ts +++ b/src/app/api/juno-engine/tickets/route.ts @@ -80,7 +80,10 @@ export async function GET() { "sentAt" FROM "EmailLog" WHERE "storeId" = ${activeStore.id} - AND "isAutomated" = TRUE + AND ( + "isAutomated" = TRUE + OR "trigger" = 'GMAIL_INCOMING' + ) ORDER BY "sentAt" DESC LIMIT 200 ` diff --git a/src/app/api/onboarding/complete/route.ts b/src/app/api/onboarding/complete/route.ts index fb1d0d2..44200f5 100644 --- a/src/app/api/onboarding/complete/route.ts +++ b/src/app/api/onboarding/complete/route.ts @@ -79,12 +79,15 @@ export async function POST(req: NextRequest) { } // Register Webhooks with Shopify - const appUrl = process.env.NEXTAUTH_URL || "https://your-tunnel-url.ngrok-free.app" + const appUrl = process.env.AUTH_URL || process.env.NEXTAUTH_URL || "https://your-tunnel-url.ngrok-free.app" const webhookTopics = ["orders/create", "orders/updated", "products/create", "products/update", "products/delete"] for (const topic of webhookTopics) { try { - await fetch(`https://${cleanDomain}/admin/api/2024-01/webhooks.json`, { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout + + const response = await fetch(`https://${cleanDomain}/admin/api/2024-01/webhooks.json`, { method: "POST", headers: { "X-Shopify-Access-Token": accessToken, @@ -96,11 +99,24 @@ export async function POST(req: NextRequest) { address: `${appUrl}/api/webhooks/shopify`, format: "json" } - }) + }), + signal: controller.signal }) - logger.info(`Registered Shopify webhook: ${topic}`) + + clearTimeout(timeoutId) + + if (!response.ok) { + const errorText = await response.text().catch(() => "") + logger.warn(`Webhook ${topic} registration returned ${response.status}: ${errorText}`) + } else { + logger.info(`Registered Shopify webhook: ${topic}`) + } } catch (err) { - logger.error(`Failed to register webhook ${topic}`, err) + if (err instanceof Error && err.name === 'AbortError') { + logger.error(`Webhook ${topic} registration timed out`) + } else { + logger.error(`Failed to register webhook ${topic}`, err) + } } } diff --git a/src/app/api/stores/[storeId]/kb/faqs/import/route.ts b/src/app/api/stores/[storeId]/kb/faqs/import/route.ts index ff8e241..2839df4 100644 --- a/src/app/api/stores/[storeId]/kb/faqs/import/route.ts +++ b/src/app/api/stores/[storeId]/kb/faqs/import/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server" import { auth } from "@/auth" import { prisma } from "@/lib/prisma" import { logger } from "@/lib/logger" -import { embedBatch } from "@/lib/kb/geminiEmbeddings" +import { embedBatch } from "@/lib/kb/bedrockEmbeddings" type FaqImportItem = { question: string diff --git a/src/app/api/stores/[storeId]/kb/faqs/route.ts b/src/app/api/stores/[storeId]/kb/faqs/route.ts index 88b0037..a8cb676 100644 --- a/src/app/api/stores/[storeId]/kb/faqs/route.ts +++ b/src/app/api/stores/[storeId]/kb/faqs/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server" import { auth } from "@/auth" import { prisma } from "@/lib/prisma" import { logger } from "@/lib/logger" -import { embedBatch } from "@/lib/kb/geminiEmbeddings" +import { embedBatch } from "@/lib/kb/bedrockEmbeddings" export async function GET( _request: Request, diff --git a/src/app/api/stores/[storeId]/kb/query/route.ts b/src/app/api/stores/[storeId]/kb/query/route.ts index ef54be7..741b48b 100644 --- a/src/app/api/stores/[storeId]/kb/query/route.ts +++ b/src/app/api/stores/[storeId]/kb/query/route.ts @@ -2,11 +2,8 @@ import { NextResponse } from "next/server" import { auth } from "@/auth" import { prisma } from "@/lib/prisma" import { logger } from "@/lib/logger" -import { embedBatch } from "@/lib/kb/geminiEmbeddings" - -const GEMINI_API_KEY = process.env.GEMINI_API_KEY -const GEMINI_MODEL = - process.env.GEMINI_CHAT_MODEL || "models/gemini-1.5-flash-latest" +import { embedBatch } from "@/lib/kb/bedrockEmbeddings" +import { chatCompletion } from "@/lib/kb/bedrockChat" type QueryBody = { query?: string @@ -124,85 +121,28 @@ export async function POST( const context = contextPieces.join("\n\n---\n\n") - if (!GEMINI_API_KEY) { - return NextResponse.json( - { - message: - "GEMINI_API_KEY is not configured, cannot generate an AI answer.", - context, - sources: chunks.map((chunk) => ({ - id: chunk.id, - type: chunk.sourceType, - documentId: chunk.documentId, - faqId: chunk.faqItemId, - similarity: chunk.similarity, - })), - }, - { status: 500 }, - ) - } - const systemPrompt = "You are an AI assistant helping answer customer and vendor questions for a specific store.\n" + "Use ONLY the provided context from the store's documents and FAQs.\n" + "If the answer is not clearly present in the context, say that you do not know.\n" + "Be concise and helpful.\n" - const response = await fetch( - "https://generativelanguage.googleapis.com/v1beta/models/" + - encodeURIComponent(GEMINI_MODEL) + - ":generateContent", - { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-goog-api-key": GEMINI_API_KEY, - }, - body: JSON.stringify({ - model: GEMINI_MODEL, - contents: [ - { - role: "user", - parts: [ - { - text: - systemPrompt + - "\n\nContext:\n" + - context + - "\n\nQuestion:\n" + - question, - }, - ], - }, - ], - }), - }, - ) + const userMessage = `Context:\n${context}\n\nQuestion:\n${question}` - if (!response.ok) { - const errorText = await response.text().catch(() => "") - logger.error("Gemini RAG answer error", { - status: response.status, - body: errorText, + let answer: string + try { + answer = await chatCompletion(userMessage, { + systemPrompt, + maxTokens: 512, }) + } catch (error) { + logger.error("Bedrock RAG answer error", error) return NextResponse.json( - { message: "Failed to generate answer from Gemini" }, + { message: "Failed to generate answer from Bedrock" }, { status: 500 }, ) } - const json = (await response.json()) as { - candidates?: Array<{ - content?: { parts?: Array<{ text?: string }> } - }> - } - - const answer = - json.candidates?.[0]?.content?.parts - ?.map((p) => p.text || "") - .join("") - .trim() || "I am unable to generate an answer at this time." - return NextResponse.json({ answer, sources: chunks.map((chunk) => ({ diff --git a/src/app/api/webhooks/gmail/route.ts b/src/app/api/webhooks/gmail/route.ts new file mode 100644 index 0000000..e8f41d7 --- /dev/null +++ b/src/app/api/webhooks/gmail/route.ts @@ -0,0 +1,119 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/prisma" +import { logger } from "@/lib/logger" +import { processIncomingEmail } from "@/lib/gmail/emailProcessor" + +/** + * POST /api/webhooks/gmail + * Gmail Push Notification Webhook + * + * This endpoint receives push notifications from Gmail when new emails arrive. + * It processes the email and generates an AI-powered reply using the knowledge base. + * + * Setup: + * 1. Register this webhook URL in Google Cloud Console + * 2. Set up Gmail watch for each user's mailbox + * 3. When emails arrive, Gmail sends a notification here + */ +export async function POST(req: NextRequest) { + try { + // Gmail sends a verification token on initial setup + const verificationToken = req.headers.get("x-goog-challenge") + if (verificationToken) { + logger.info("Gmail webhook verification request received") + // Return the verification token to confirm webhook ownership + return new NextResponse(verificationToken, { status: 200 }) + } + + // Parse the notification payload + const body = await req.json().catch(() => ({})) + const messageData = body.message?.data + + if (!messageData) { + logger.warn("Gmail webhook received without message data") + return NextResponse.json({ message: "No message data" }, { status: 200 }) + } + + // Decode base64url encoded message data + const decodedData = Buffer.from(messageData, "base64") + .toString("utf-8") + + const notification = JSON.parse(decodedData) + const emailAddress = notification.emailAddress + const historyId = notification.historyId + + logger.info("Gmail webhook notification received", { + emailAddress, + historyId, + notification + }) + + // Find the user by Gmail email address + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const integration = await (prisma as any).gmailIntegration.findFirst({ + where: { + email: emailAddress + }, + include: { + user: { + include: { + stores: { + where: { isActive: true }, + take: 1 + } + } + } + } + }) + + if (!integration) { + logger.warn("Gmail integration not found for email", { emailAddress }) + return NextResponse.json( + { message: "Integration not found" }, + { status: 200 } // Return 200 to prevent retries + ) + } + + const userId = integration.userId + const storeId = integration.user.stores[0]?.id || null + + // Process the incoming email + // This will fetch the email, analyze it, and send an AI-powered or fallback reply + const result = await processIncomingEmail(userId, storeId, historyId) + + logger.info("Gmail webhook processing result", { + userId, + storeId, + historyId, + success: result.success, + error: result.error, + }) + + return NextResponse.json({ + success: result.success, + error: result.error, + message: result.success + ? "Email processed successfully" + : "Email processing completed with errors (see logs)" + }) + } catch (error) { + logger.error("Gmail webhook error", error) + // Return 200 to prevent Gmail from retrying + // Log errors internally for debugging + return NextResponse.json( + { message: "Webhook processed with errors" }, + { status: 200 } + ) + } +} + +/** + * GET /api/webhooks/gmail + * Health check endpoint + */ +export async function GET() { + return NextResponse.json({ + status: "ok", + message: "Gmail webhook endpoint is active" + }) +} diff --git a/src/app/dashboard/juno-engine/email-templates/page.tsx b/src/app/dashboard/juno-engine/email-templates/page.tsx index 8bebcbd..9e460d9 100644 --- a/src/app/dashboard/juno-engine/email-templates/page.tsx +++ b/src/app/dashboard/juno-engine/email-templates/page.tsx @@ -1,40 +1,193 @@ "use client" -import { useState } from "react" -import { - FileText, - Search, +import { useEffect, useMemo, useState } from "react" +import { + FileText, + Search, Plus, Loader2, Edit, Trash2, - Copy, - Eye } from "lucide-react" +import { renderTemplate } from "@/lib/templates/simpleRenderer" -interface EmailTemplate { +type TemplateIntent = "general" | "shipping_delay" | "refund_request" | "question" +type TemplateMood = "neutral" | "angry" | "confused" | "happy" + +interface Template { id: string + storeId: string name: string - subject: string - category: "support" | "order" | "marketing" | "general" + intent: TemplateIntent | string + mood: TemplateMood | string + description: string | null + body: string + variables?: unknown + isHtml: boolean updatedAt: string } +const INTENT_OPTIONS: { value: TemplateIntent; label: string }[] = [ + { value: "general", label: "General" }, + { value: "shipping_delay", label: "Shipping delay" }, + { value: "refund_request", label: "Refund request" }, + { value: "question", label: "General question" }, +] + +const MOOD_OPTIONS: { value: TemplateMood; label: string }[] = [ + { value: "neutral", label: "Neutral" }, + { value: "angry", label: "Frustrated / Angry" }, + { value: "confused", label: "Confused" }, + { value: "happy", label: "Happy" }, +] + +const exampleContext = { + customer_name: "Alex", + customer_email: "alex@example.com", + store_name: "JUNOVendor", + store_email: "support@junovendor.com", + subject: "Where is my order?", + ticket_summary: "Customer is asking about a shipping delay on order #1234.", +} + export default function EmailTemplatesPage() { const [searchTerm, setSearchTerm] = useState("") - const [templates, setTemplates] = useState([]) + const [templates, setTemplates] = useState([]) const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) - const getCategoryStyles = (category: string) => { - switch (category) { - case "support": return "bg-blue-500/20 text-blue-400" - case "order": return "bg-emerald-500/20 text-emerald-400" - case "marketing": return "bg-purple-500/20 text-purple-400" - case "general": return "bg-slate-500/20 text-slate-400" - default: return "bg-white/10 text-slate-400" + const [editing, setEditing] = useState