Back to skills
SkillHub ClubRun DevOpsFull StackDevOps

vercel-deploy

Deploy applications and websites to Vercel instantly. Use when asked to "Deploy my app", "Deploy this to production", "Create a preview deployment", or "Push this live". No authentication required - returns preview URL and claimable deployment link.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
68
Hot score
92
Updated
March 20, 2026
Overall rating
C3.5
Composite score
3.5
Best-practice grade
B73.6

Install command

npx @skill-hub/cli install supercent-io-skills-template-vercel-deploy

Repository

supercent-io/skills-template

Skill path: .agent-skills/vercel-deploy

Deploy applications and websites to Vercel instantly. Use when asked to "Deploy my app", "Deploy this to production", "Create a preview deployment", or "Push this live". No authentication required - returns preview URL and claimable deployment link.

Open repository

Best for

Primary workflow: Run DevOps.

Technical facets: Full Stack, DevOps.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: supercent-io.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install vercel-deploy into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/supercent-io/skills-template before adding vercel-deploy to shared team environments
  • Use vercel-deploy for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: vercel-deploy
description: "Deploy applications and websites to Vercel instantly. Use when asked to \"Deploy my app\", \"Deploy this to production\", \"Create a preview deployment\", or \"Push this live\". No authentication required - returns preview URL and claimable deployment link."
metadata:
  author: vercel
  version: 1.0.0
  tags: deployment, vercel, preview, production, hosting, serverless
  platforms: Claude
---


# Vercel Deploy

Deploy any project to Vercel instantly. No authentication required.

## When to use this skill

- **App deployment**: when asked "Deploy my app"
- **Preview deployment**: when asked "Create a preview deployment"
- **Production deployment**: when asked "Deploy this to production"
- **Share link**: when asked "Deploy and give me the link"

## How It Works

1. Packages your project into a tarball (excludes `node_modules` and `.git`)
2. Auto-detects framework from `package.json`
3. Uploads to deployment service
4. Returns **Preview URL** (live site) and **Claim URL** (transfer to your Vercel account)

## Instructions

### Step 1: Prepare Project

Confirm the project directory to deploy.

**Supported frameworks**:
- **React**: Next.js, Gatsby, Create React App, Remix, React Router
- **Vue**: Nuxt, Vitepress, Vuepress, Gridsome
- **Svelte**: SvelteKit, Svelte, Sapper
- **Other Frontend**: Astro, Solid Start, Angular, Ember, Preact, Docusaurus
- **Backend**: Express, Hono, Fastify, NestJS, Elysia, h3, Nitro
- **Build Tools**: Vite, Parcel
- **And more**: Blitz, Hydrogen, RedwoodJS, Storybook, Sanity, etc.

### Step 2: Run Deployment

**Use the script** (claude.ai environment):
```bash
bash /mnt/skills/user/vercel-deploy/scripts/deploy.sh [path]
```

**Arguments:**
- `path` - Directory to deploy, or a `.tgz` file (defaults to current directory)

**Examples:**
```bash
# Deploy current directory
bash /mnt/skills/user/vercel-deploy/scripts/deploy.sh

# Deploy specific project
bash /mnt/skills/user/vercel-deploy/scripts/deploy.sh /path/to/project

# Deploy existing tarball
bash /mnt/skills/user/vercel-deploy/scripts/deploy.sh /path/to/project.tgz
```

### Step 3: Verify Result

On successful deployment, two URLs are returned:
- **Preview URL**: live site you can access immediately
- **Claim URL**: transfer this deployment to your Vercel account

## Output Format

### Console Output

```
Preparing deployment...
Detected framework: nextjs
Creating deployment package...
Deploying...
āœ“ Deployment successful!

Preview URL: https://skill-deploy-abc123.vercel.app
Claim URL:   https://vercel.com/claim-deployment?code=...
```

### JSON Output (for automation)

```json
{
  "previewUrl": "https://skill-deploy-abc123.vercel.app",
  "claimUrl": "https://vercel.com/claim-deployment?code=...",
  "deploymentId": "dpl_...",
  "projectId": "prj_..."
}
```

## Static HTML Projects

For projects without a `package.json`:
- If there's a single `.html` file not named `index.html`, it gets renamed automatically
- This ensures the page is served at the root URL (`/`)

## Present Results to User

Always show both URLs:

```
āœ“ Deployment successful!

Preview URL: https://skill-deploy-abc123.vercel.app
Claim URL:   https://vercel.com/claim-deployment?code=...

View your site at the Preview URL.
To transfer this deployment to your Vercel account, visit the Claim URL.
```

## Troubleshooting

### Network Egress Error

If deployment fails due to network restrictions (common on claude.ai), tell the user:

```
Deployment failed due to network restrictions. To fix this:

1. Go to https://claude.ai/settings/capabilities
2. Add *.vercel.com to the allowed domains
3. Try deploying again
```

### Framework Not Detected

If the framework is not detected:
1. Check that `package.json` exists
2. Check that your dependencies include the framework package
3. Manually set the `framework` parameter

## Constraints

### Required Rules (MUST)

1. **Show both URLs**: show both the Preview URL and Claim URL to the user
2. **Framework detection**: auto-detect from package.json
3. **Show error messages**: show a clear error message if deployment fails

### Prohibited (MUST NOT)

1. **Include node_modules**: do not include node_modules in the tarball
2. **Include .git**: do not include the .git directory in the tarball
3. **Hardcode credentials**: no authentication required (claimable deploy)

## Best practices

1. **Automatic framework detection**: pick optimal settings by analyzing package.json
2. **Clean Tarball**: exclude node_modules and .git for faster uploads
3. **Clear output**: clearly distinguish the Preview URL and Claim URL

## References

- [Vercel Documentation](https://vercel.com/docs)
- [Vercel CLI](https://vercel.com/docs/cli)

## Metadata

### Version
- **Current version**: 1.0.0
- **Last updated**: 2026-01-22
- **Supported platforms**: Claude (claude.ai)
- **Source**: vercel/agent-skills

### Related Skills
- [deployment-automation](../deployment-automation/SKILL.md): CI/CD and Docker/K8s deployments

### Tags
`#deployment` `#vercel` `#preview` `#production` `#hosting` `#serverless` `#infrastructure`


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### ../deployment-automation/SKILL.md

```markdown
---
name: deployment-automation
description: Automate application deployment to cloud platforms and servers. Use when setting up CI/CD pipelines, deploying to Docker/Kubernetes, or configuring cloud infrastructure. Handles GitHub Actions, Docker, Kubernetes, AWS, Vercel, and deployment best practices.
metadata:
  tags: deployment, CI/CD, Docker, Kubernetes, AWS, GitHub-Actions, automation
  platforms: Claude, ChatGPT, Gemini
---


# Deployment Automation


## When to use this skill

- **New Projects**: Set up automated deployment from scratch
- **Manual Deployment Improvement**: Automate repetitive manual tasks
- **Multi-Environment**: Separate dev, staging, and production environments
- **Scaling**: Introduce Kubernetes to handle traffic growth

## Instructions

### Step 1: Docker Containerization

Package the application as a Docker image.

**Dockerfile** (Node.js app):
```dockerfile
# Multi-stage build for smaller image size
FROM node:18-alpine AS builder

WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm ci --only=production

# Copy source code
COPY . .

# Build application (if needed)
RUN npm run build

# Production stage
FROM node:18-alpine

WORKDIR /app

# Copy only necessary files from builder
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
USER nodejs

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js

# Start application
CMD ["node", "dist/index.js"]
```

**.dockerignore**:
```
node_modules
npm-debug.log
.git
.env
.env.local
dist
build
coverage
.DS_Store
```

**Build and Run**:
```bash
# Build image
docker build -t myapp:latest .

# Run container
docker run -d -p 3000:3000 --name myapp-container myapp:latest

# Check logs
docker logs myapp-container

# Stop and remove
docker stop myapp-container
docker rm myapp-container
```

### Step 2: GitHub Actions CI/CD

Automatically runs tests and deploys on code push.

**.github/workflows/deploy.yml**:
```yaml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '18'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test -- --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix={{branch}}-
            type=semver,pattern={{version}}
            latest

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Deploy to production
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            cd /app
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            docker-compose up -d --no-deps --build web
            docker image prune -f
```

### Step 3: Kubernetes Deployment

Implement scalable container orchestration.

**k8s/deployment.yaml**:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: production
  labels:
    app: myapp
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: ghcr.io/username/myapp:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: myapp-secrets
              key: database-url
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
  namespace: production
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: LoadBalancer

---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
```

**Deployment Script** (deploy.sh):
```bash
#!/bin/bash
set -e

# Variables
NAMESPACE="production"
IMAGE_TAG="${1:-latest}"

echo "Deploying myapp:${IMAGE_TAG} to ${NAMESPACE}..."

# Apply Kubernetes manifests
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml

# Update image
kubectl set image deployment/myapp myapp=ghcr.io/username/myapp:${IMAGE_TAG} -n ${NAMESPACE}

# Wait for rollout
kubectl rollout status deployment/myapp -n ${NAMESPACE} --timeout=5m

# Verify
kubectl get pods -n ${NAMESPACE} -l app=myapp

echo "Deployment completed successfully!"
```

### Step 4: Vercel/Netlify (Frontend)

Simply deploy static sites and Next.js apps.

**vercel.json**:
```json
{
  "version": 2,
  "builds": [
    {
      "src": "package.json",
      "use": "@vercel/next"
    }
  ],
  "env": {
    "DATABASE_URL": "@database-url",
    "API_KEY": "@api-key"
  },
  "regions": ["sin1", "icn1"],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        }
      ]
    }
  ],
  "redirects": [
    {
      "source": "/old-path",
      "destination": "/new-path",
      "permanent": true
    }
  ]
}
```

**CLI Deployment**:
```bash
# Install Vercel CLI
npm i -g vercel

# Login
vercel login

# Deploy to preview
vercel

# Deploy to production
vercel --prod

# Set environment variable
vercel env add DATABASE_URL
```

### Step 5: Zero-Downtime Deployment Strategy

Deploy new versions without service interruption.

**Blue-Green Deployment** (docker-compose):
```yaml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app-blue
      - app-green

  app-blue:
    image: myapp:blue
    environment:
      - NODE_ENV=production
      - COLOR=blue

  app-green:
    image: myapp:green
    environment:
      - NODE_ENV=production
      - COLOR=green
```

**switch.sh** (Blue/Green Switch):
```bash
#!/bin/bash

CURRENT_COLOR=$(cat current_color.txt)
NEW_COLOR=$([[ "$CURRENT_COLOR" == "blue" ]] && echo "green" || echo "blue")

# Deploy new version to inactive environment
docker-compose up -d app-${NEW_COLOR}

# Wait for health check
sleep 10

# Health check
if curl -f http://localhost:8080/health; then
  # Update nginx to point to new environment
  sed -i "s/${CURRENT_COLOR}/${NEW_COLOR}/g" nginx.conf
  docker-compose exec nginx nginx -s reload

  # Update current color
  echo ${NEW_COLOR} > current_color.txt

  # Stop old environment after 5 minutes (rollback window)
  sleep 300
  docker-compose stop app-${CURRENT_COLOR}

  echo "Deployment successful! Switched to ${NEW_COLOR}"
else
  echo "Health check failed! Keeping ${CURRENT_COLOR}"
  docker-compose stop app-${NEW_COLOR}
  exit 1
fi
```

## Output format

### Deployment Checklist

```markdown
## Deployment Checklist

### Pre-Deployment
- [ ] All tests passing (unit, integration, E2E)
- [ ] Code review approved
- [ ] Environment variables configured
- [ ] Database migrations ready
- [ ] Rollback plan documented

### Deployment
- [ ] Docker image built and tagged
- [ ] Image pushed to container registry
- [ ] Kubernetes manifests applied
- [ ] Rolling update started
- [ ] Pods healthy and ready

### Post-Deployment
- [ ] Health check endpoint responding
- [ ] Metrics/logs monitoring active
- [ ] Performance baseline established
- [ ] Old pods terminated (after grace period)
- [ ] Deployment documented in changelog
```

## Constraints

### Required Rules (MUST)

1. **Health Checks**: Health check endpoint for all services
   ```typescript
   app.get('/health', (req, res) => {
     res.status(200).json({ status: 'ok' });
   });
   ```

2. **Graceful Shutdown**: Handle SIGTERM signal
   ```javascript
   process.on('SIGTERM', async () => {
     console.log('SIGTERM received, shutting down gracefully');
     await server.close();
     await db.close();
     process.exit(0);
   });
   ```

3. **Environment Variable Separation**: No hardcoding; use .env files

### Prohibited Rules (MUST NOT)

1. **No Committing Secrets**: Never commit API keys or passwords to Git
2. **No Debug Mode in Production**: `NODE_ENV=production` is required
3. **Avoid latest tag only**: Use version tags (v1.0.0, sha-abc123)

## Best practices

1. **Multi-stage Docker builds**: Minimize image size
2. **Immutable infrastructure**: Redeploy instead of modifying servers
3. **Blue-Green deployment**: Zero-downtime deployment and easy rollback
4. **Monitoring required**: Prometheus, Grafana, Datadog

## References

- [Docker Docs](https://docs.docker.com/)
- [Kubernetes Docs](https://kubernetes.io/docs/)
- [GitHub Actions](https://docs.github.com/en/actions)
- [Vercel](https://vercel.com/docs)
- [12 Factor App](https://12factor.net/)

## Metadata

### Version
- **Current Version**: 1.0.0
- **Last Updated**: 2025-01-01
- **Compatible Platforms**: Claude, ChatGPT, Gemini

### Related Skills
- [monitoring](../monitoring/SKILL.md): Post-deployment monitoring
- [security](../security/SKILL.md): Deployment security

### Tags
`#deployment` `#CI/CD` `#Docker` `#Kubernetes` `#automation` `#infrastructure`

## Examples

### Example 1: Basic usage
<!-- Add example content here -->

### Example 2: Advanced usage
<!-- Add advanced example content here -->

```



---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### scripts/deploy.sh

```bash
#!/bin/bash

# Vercel Deployment Script (via claimable deploy endpoint)
# Usage: ./deploy.sh [project-path]
# Returns: JSON with previewUrl, claimUrl, deploymentId, projectId

set -e

DEPLOY_ENDPOINT="https://claude-skills-deploy.vercel.com/api/deploy"

# Detect framework from package.json
detect_framework() {
    local pkg_json="$1"

    if [ ! -f "$pkg_json" ]; then
        echo "null"
        return
    fi

    local content=$(cat "$pkg_json")

    # Helper to check if a package exists in dependencies or devDependencies
    has_dep() {
        echo "$content" | grep -q "\"$1\""
    }

    # Order matters - check more specific frameworks first

    # Blitz
    if has_dep "blitz"; then echo "blitzjs"; return; fi

    # Next.js
    if has_dep "next"; then echo "nextjs"; return; fi

    # Gatsby
    if has_dep "gatsby"; then echo "gatsby"; return; fi

    # Remix
    if has_dep "@remix-run/"; then echo "remix"; return; fi

    # React Router (v7 framework mode)
    if has_dep "@react-router/"; then echo "react-router"; return; fi

    # TanStack Start
    if has_dep "@tanstack/start"; then echo "tanstack-start"; return; fi

    # Astro
    if has_dep "astro"; then echo "astro"; return; fi

    # Hydrogen (Shopify)
    if has_dep "@shopify/hydrogen"; then echo "hydrogen"; return; fi

    # SvelteKit
    if has_dep "@sveltejs/kit"; then echo "sveltekit-1"; return; fi

    # Svelte (standalone)
    if has_dep "svelte"; then echo "svelte"; return; fi

    # Nuxt
    if has_dep "nuxt"; then echo "nuxtjs"; return; fi

    # Vue with Vitepress
    if has_dep "vitepress"; then echo "vitepress"; return; fi

    # Vue with Vuepress
    if has_dep "vuepress"; then echo "vuepress"; return; fi

    # Gridsome
    if has_dep "gridsome"; then echo "gridsome"; return; fi

    # SolidStart
    if has_dep "@solidjs/start"; then echo "solidstart-1"; return; fi

    # Docusaurus
    if has_dep "@docusaurus/core"; then echo "docusaurus-2"; return; fi

    # RedwoodJS
    if has_dep "@redwoodjs/"; then echo "redwoodjs"; return; fi

    # Hexo
    if has_dep "hexo"; then echo "hexo"; return; fi

    # Eleventy
    if has_dep "@11ty/eleventy"; then echo "eleventy"; return; fi

    # Angular / Ionic Angular
    if has_dep "@ionic/angular"; then echo "ionic-angular"; return; fi
    if has_dep "@angular/core"; then echo "angular"; return; fi

    # Ionic React
    if has_dep "@ionic/react"; then echo "ionic-react"; return; fi

    # Create React App
    if has_dep "react-scripts"; then echo "create-react-app"; return; fi

    # Ember
    if has_dep "ember-cli" || has_dep "ember-source"; then echo "ember"; return; fi

    # Dojo
    if has_dep "@dojo/framework"; then echo "dojo"; return; fi

    # Polymer
    if has_dep "@polymer/"; then echo "polymer"; return; fi

    # Preact
    if has_dep "preact"; then echo "preact"; return; fi

    # Stencil
    if has_dep "@stencil/core"; then echo "stencil"; return; fi

    # UmiJS
    if has_dep "umi"; then echo "umijs"; return; fi

    # Sapper (legacy Svelte)
    if has_dep "sapper"; then echo "sapper"; return; fi

    # Saber
    if has_dep "saber"; then echo "saber"; return; fi

    # Sanity
    if has_dep "sanity"; then echo "sanity-v3"; return; fi
    if has_dep "@sanity/"; then echo "sanity"; return; fi

    # Storybook
    if has_dep "@storybook/"; then echo "storybook"; return; fi

    # NestJS
    if has_dep "@nestjs/core"; then echo "nestjs"; return; fi

    # Elysia
    if has_dep "elysia"; then echo "elysia"; return; fi

    # Hono
    if has_dep "hono"; then echo "hono"; return; fi

    # Fastify
    if has_dep "fastify"; then echo "fastify"; return; fi

    # h3
    if has_dep "h3"; then echo "h3"; return; fi

    # Nitro
    if has_dep "nitropack"; then echo "nitro"; return; fi

    # Express
    if has_dep "express"; then echo "express"; return; fi

    # Vite (generic - check last among JS frameworks)
    if has_dep "vite"; then echo "vite"; return; fi

    # Parcel
    if has_dep "parcel"; then echo "parcel"; return; fi

    # No framework detected
    echo "null"
}

# Parse arguments
INPUT_PATH="${1:-.}"

# Create temp directory for packaging
TEMP_DIR=$(mktemp -d)
TARBALL="$TEMP_DIR/project.tgz"
CLEANUP_TEMP=true

cleanup() {
    if [ "$CLEANUP_TEMP" = true ]; then
        rm -rf "$TEMP_DIR"
    fi
}
trap cleanup EXIT

echo "Preparing deployment..." >&2

# Check if input is a .tgz file or a directory
FRAMEWORK="null"

if [ -f "$INPUT_PATH" ] && [[ "$INPUT_PATH" == *.tgz ]]; then
    # Input is already a tarball, use it directly
    echo "Using provided tarball..." >&2
    TARBALL="$INPUT_PATH"
    CLEANUP_TEMP=false
    # Can't detect framework from tarball, leave as null
elif [ -d "$INPUT_PATH" ]; then
    # Input is a directory, need to tar it
    PROJECT_PATH=$(cd "$INPUT_PATH" && pwd)

    # Detect framework from package.json
    FRAMEWORK=$(detect_framework "$PROJECT_PATH/package.json")

    # Check if this is a static HTML project (no package.json)
    if [ ! -f "$PROJECT_PATH/package.json" ]; then
        # Find HTML files in root
        HTML_FILES=$(find "$PROJECT_PATH" -maxdepth 1 -name "*.html" -type f)
        HTML_COUNT=$(echo "$HTML_FILES" | grep -c . || echo 0)

        # If there's exactly one HTML file and it's not index.html, rename it
        if [ "$HTML_COUNT" -eq 1 ]; then
            HTML_FILE=$(echo "$HTML_FILES" | head -1)
            BASENAME=$(basename "$HTML_FILE")
            if [ "$BASENAME" != "index.html" ]; then
                echo "Renaming $BASENAME to index.html..." >&2
                mv "$HTML_FILE" "$PROJECT_PATH/index.html"
            fi
        fi
    fi

    # Create tarball of the project (excluding node_modules and .git)
    echo "Creating deployment package..." >&2
    tar -czf "$TARBALL" -C "$PROJECT_PATH" --exclude='node_modules' --exclude='.git' .
else
    echo "Error: Input must be a directory or a .tgz file" >&2
    exit 1
fi

if [ "$FRAMEWORK" != "null" ]; then
    echo "Detected framework: $FRAMEWORK" >&2
fi

# Deploy
echo "Deploying..." >&2
RESPONSE=$(curl -s -X POST "$DEPLOY_ENDPOINT" -F "file=@$TARBALL" -F "framework=$FRAMEWORK")

# Check for error in response
if echo "$RESPONSE" | grep -q '"error"'; then
    ERROR_MSG=$(echo "$RESPONSE" | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
    echo "Error: $ERROR_MSG" >&2
    exit 1
fi

# Extract URLs from response
PREVIEW_URL=$(echo "$RESPONSE" | grep -o '"previewUrl":"[^"]*"' | cut -d'"' -f4)
CLAIM_URL=$(echo "$RESPONSE" | grep -o '"claimUrl":"[^"]*"' | cut -d'"' -f4)

if [ -z "$PREVIEW_URL" ]; then
    echo "Error: Could not extract preview URL from response" >&2
    echo "$RESPONSE" >&2
    exit 1
fi

echo "" >&2
echo "Deployment successful!" >&2
echo "" >&2
echo "Preview URL: $PREVIEW_URL" >&2
echo "Claim URL:   $CLAIM_URL" >&2
echo "" >&2

# Output JSON for programmatic use
echo "$RESPONSE"

```

vercel-deploy | SkillHub