🎯

exe

🎯Skill

from popmechanic/vibes-cli

VibeIndex|
What it does

Deploys Vibes apps to exe.dev VM hosting with automatic nginx setup, SSH automation, and multi-tenant subdomain isolation.

πŸ“¦

Part of

popmechanic/vibes-cli(8 items)

exe

Installation

Node.jsRun Node.js server
node deploy-exe.js --clerk-key "$(cat clerk-public-key.pem)" ...
Node.jsRun Node.js server
node deploy-exe.js --clerk-key "-----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----" ...
πŸ“– Extracted from docs: popmechanic/vibes-cli
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Deploy a Vibes app to exe.dev VM hosting. Uses nginx on persistent VMs with SSH automation. Supports client-side multi-tenancy via subdomain-based Fireproof database isolation.

Deploy to exe.dev

Deploy your Vibes app to exe.dev, a VM hosting platform with persistent storage and HTTPS by default.

Prerequisites

  1. SSH key in ~/.ssh/ (id_ed25519, id_rsa, or id_ecdsa)
  2. exe.dev account - run ssh exe.dev once to create your account and verify email
  3. Generated Vibes app - an index.html file ready to deploy

Gather Config Upfront

Use AskUserQuestion to collect deployment config before running the deploy script.

Use the AskUserQuestion tool with these questions:

```

Question 1: "What VM name should we use? (becomes yourname.exe.xyz)"

Header: "VM Name"

Options: Suggest based on app name from context + user enters via "Other"

Question 2: "Which file should we deploy?"

Header: "File"

Options: ["index.html (default)", "Other path"]

Question 3: "Does this app need AI features?"

Header: "AI"

Options: ["No", "Yes - I have an OpenRouter key"]

Question 4: "Is this a SaaS app with subdomain claiming?"

Header: "Registry"

Options: ["No - simple static deploy", "Yes - need Clerk keys for registry"]

```

#### After Receiving Answers

  1. If AI enabled, ask for the OpenRouter API key
  2. If Registry enabled, ask for Clerk PEM public key and webhook secret
  3. Proceed immediately to deploy - no more questions

Quick Deploy

```bash

cd "${CLAUDE_PLUGIN_ROOT}/scripts" && [ -d node_modules ] || npm install

node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp --file index.html

```

What It Does

  1. Creates VM on exe.dev via SSH CLI
  2. Starts nginx (pre-installed on exeuntu image)
  3. Uploads your index.html to /var/www/html/
  4. Generates HANDOFF.md - context document for remote Claude

- For Connect-enabled deployments, includes full Docker setup documentation (commands, troubleshooting, service endpoints)

  1. Makes VM public via ssh exe.dev share set-public
  2. Verifies public access at https://myapp.exe.xyz

AI-Enabled Apps

For apps using the useAI hook, deploy with the --ai-key flag:

```bash

node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp --file index.html --ai-key "sk-or-v1-..."

```

This sets up a secure AI proxy:

  1. Installs Bun runtime
  2. Creates /home/exedev/proxy.js - proxies /api/ai/* to OpenRouter
  3. Configures systemd service for the proxy
  4. Adds nginx reverse proxy from port 80/443 to Bun (port 3001)

IMPORTANT: Do not manually set up AI proxying. Manual nginx config changes can overwrite SSL settings and miss the Bun/proxy.js service. Always use the deploy script with --ai-key.

#### Multi-Tenant AI Apps

For SaaS apps with per-tenant AI:

```bash

node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp --file index.html --ai-key "sk-or-v1-..." --multi-tenant

```

Registry Server

For SaaS apps using subdomain claiming (from /vibes:sell), deploy with Clerk credentials:

```bash

node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp --file index.html \

--clerk-key "$(cat clerk-public-key.pem)" \

--clerk-webhook-secret "whsec_xxx" \

--reserved "admin,api,billing"

```

This sets up a subdomain registry server:

  1. Installs Bun runtime to /usr/local/bin/bun
  2. Creates /var/www/registry-server.ts with Clerk JWT verification
  3. Configures systemd service (port 3002)
  4. Adds nginx proxy for /registry.json, /check/*, /claim, /webhook

#### Registry Endpoints

| Endpoint | Method | Auth | Description |

|----------|--------|------|-------------|

| /registry.json | GET | None | Public read of all claims |

| /check/{subdomain} | GET | None | Check availability |

| /claim | POST | Bearer JWT | Claim subdomain for user |

| /webhook | POST | Svix sig | Clerk subscription events |

#### Getting the Clerk Public Key

The registry server needs Clerk's PEM public key to verify JWTs for the /claim endpoint.

Option 1: From Clerk Dashboard

  1. Go to Clerk Dashboard β†’ API Keys
  2. Scroll to "PEM Public Key" or click "Show JWT Public Key"
  3. Copy the full key (starts with -----BEGIN PUBLIC KEY-----)

Option 2: From JWKS endpoint

```bash

# Get your Clerk frontend API domain from dashboard

curl https://YOUR_CLERK_DOMAIN/.well-known/jwks.json

```

Then convert the JWK to PEM format using an online tool or jose CLI.

Passing to deploy script:

```bash

# From a file

node deploy-exe.js --clerk-key "$(cat clerk-public-key.pem)" ...

# Inline (escape newlines)

node deploy-exe.js --clerk-key "-----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----" ...

```

Manual configuration on server:

```bash

ssh myapp.exe.dev

sudo nano /etc/registry.env

# Add: CLERK_PEM_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"

sudo systemctl restart vibes-registry

```

Continue Development on the VM

Claude is pre-installed on exe.dev VMs. After deployment, you can continue development remotely:

```bash

ssh myapp.exe.dev -t "cd /var/www/html && claude"

```

The HANDOFF.md file provides context about what was built, so Claude can continue meaningfully.

#### Manual Public Access

If the deploy script doesn't make the VM public automatically, run:

```bash

ssh exe.dev share set-public myapp

```

Multi-Tenant Apps

For apps that need tenant isolation (e.g., alice.myapp.com, bob.myapp.com):

#### Client-Side Isolation

The same index.html serves all subdomains. JavaScript reads the hostname and uses the subdomain as a Fireproof database prefix:

```javascript

// In your app:

const hostname = window.location.hostname;

const subdomain = hostname.split('.')[0];

const dbName = myapp-${subdomain};

// Each subdomain gets its own Fireproof database

const { database } = useFireproof(dbName);

```

#### Custom Domain Setup

  1. Add --domain flag:

```bash

node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp --domain myapp.com

```

  1. Configure wildcard DNS at your DNS provider:

```

*.myapp.com CNAME myapp.exe.xyz

myapp.com ALIAS exe.xyz

```

  1. Set up wildcard SSL on the VM:

```bash

ssh myapp.exe.dev

sudo apt install certbot

sudo certbot certonly --manual --preferred-challenges dns \

-d "myapp.com" -d "*.myapp.com"

```

CLI Options

| Option | Description |

|--------|-------------|

| --name | VM name (required) |

| --file | HTML file to deploy (default: index.html) |

| --domain | Custom domain for wildcard setup |

| --ai-key | OpenRouter API key for AI features |

| --multi-tenant | Enable subdomain-based multi-tenancy |

| --tenant-limit <$> | Credit limit per tenant in dollars (default: 5) |

| --clerk-key | Clerk PEM public key for JWT verification |

| --clerk-webhook-secret | Clerk webhook signing secret |

| --reserved | Comma-separated reserved subdomain names |

| --preallocated | Pre-claimed subdomains (format: sub:user_id) |

| --dry-run | Show commands without executing |

| --skip-verify | Skip deployment verification |

Redeployment

After making changes, redeploy with:

```bash

node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp

```

SSH Access

Access your VM directly:

```bash

ssh myapp.exe.dev

```

Architecture

```

exe.dev VM (exeuntu image)

β”œβ”€β”€ nginx (serves all subdomains via server_name _)

β”œβ”€β”€ claude (pre-installed CLI)

β”œβ”€β”€ /usr/local/bin/bun ← Bun runtime (system-wide)

β”œβ”€β”€ /var/www/html/

β”‚ β”œβ”€β”€ index.html ← Your Vibes app

β”‚ └── HANDOFF.md ← Context for remote Claude

β”œβ”€β”€ (with --ai-key)

β”‚ β”œβ”€β”€ /opt/vibes/proxy.js ← AI proxy service (port 3001)

β”‚ └── vibes-proxy.service ← systemd unit

└── (with --clerk-key)

β”œβ”€β”€ /var/www/registry-server.ts ← Registry service (port 3002)

β”œβ”€β”€ /var/www/html/registry.json ← Subdomain claims data

└── vibes-registry.service ← systemd unit

```

#### Port Assignments

| Service | Port | Purpose |

|---------|------|---------|

| AI Proxy | 3001 | OpenRouter proxy for useAI hook |

| Registry | 3002 | Subdomain claim/check API |

  • No server-side logic - pure static hosting (unless using AI proxy)
  • Persistent disk - survives restarts
  • HTTPS by default - exe.dev handles SSL for *.exe.xyz
  • Claude pre-installed - continue development on the VM

Post-Deploy Debugging

After deployment, always work with local files - they are the source of truth. SSHing to read deployed files is slow and wastes tokens.

| Task | Use Local | Use SSH |

|------|-----------|---------|

| Editing/debugging code | βœ… Always | ❌ Never |

| Checking console errors | βœ… Local file | ❌ No need |

| Verifying deploy | ❌ | βœ… curl https://vm.exe.xyz |

| Server-specific issues | ❌ | βœ… Only if local works but remote doesn't |

To redeploy after local fixes:

```bash

node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name --file index.html

```

SSL Configuration

The deploy script preserves existing SSL by using include files for AI proxy config. When manually editing nginx:

  1. Never replace the entire config - only add/modify specific blocks
  2. Check for SSL first: Look for listen 443 ssl in the config
  3. Use includes: Put new configs in /etc/nginx/conf.d/ or separate files
  4. Test before reload: sudo nginx -t

Manual File Transfer to VMs

When manually transferring files (outside the deploy script), use the two-stage pattern.

Key distinction:

  • exe.dev = orchestrator (for VM management: ssh exe.dev new, ssh exe.dev share)
  • .exe.xyz = actual VM (for file operations, SSH access)

Reliable pattern:

```bash

# Upload: SCP to server temp β†’ sudo move to /var/www/html/

scp index.html myapp.exe.xyz:/tmp/

ssh myapp.exe.xyz "sudo cp /tmp/index.html /var/www/html/"

# Download: Direct SCP works fine

scp myapp.exe.xyz:/var/www/html/index.html ./downloaded.html

```

Why server-side temp?

  • Direct SCP to /var/www/html/ fails (permission denied - owned by www-data)
  • Server /tmp/ is world-writable, so SCP succeeds
  • sudo cp moves file with correct ownership

Common mistakes:

| Mistake | Error | Fix |

|---------|-------|-----|

| ssh exe.dev cat /var/www/... | "No VMs found" | Use ssh .exe.xyz |

| scp file vm:/var/www/html/ | Permission denied | Use temp + sudo pattern |

| Forgetting sudo for /var/www | Permission denied | Always sudo cp for www-data dirs |

Quick reference commands:

```bash

# Connect to VM

ssh .exe.xyz

# Read file

ssh .exe.xyz "cat /var/www/html/index.html"

# Upload file (two-stage)

scp index.html .exe.xyz:/tmp/

ssh .exe.xyz "sudo cp /tmp/index.html /var/www/html/"

# Download file

scp .exe.xyz:/var/www/html/index.html ./downloaded.html

# Verify

ssh .exe.xyz "head -20 /var/www/html/index.html"

```

---

What's Next?

After successful deployment, present these options using AskUserQuestion:

```

Question: "Your app is live at https://${name}.exe.xyz! What's next?"

Header: "Next"

Options:

  • Label: "Share my URL"

Description: "Get the shareable link for your app. I'll confirm the public URL and you can send it to anyone - they'll see your app immediately with full functionality."

  • Label: "Make changes and redeploy"

Description: "Continue iterating locally. Edit your files here, then run deploy again to push updates. The VM keeps running so there's zero downtime during updates."

  • Label: "Continue development on VM"

Description: "Work directly on the server. SSH in and use the pre-installed Claude to make changes live. Great for server-specific debugging or when you want changes to persist immediately."

  • Label: "I'm done for now"

Description: "Wrap up this session. Your app stays live at the URL - it runs 24/7 on exe.dev's persistent VMs. Come back anytime to make updates."

```

After user responds:

  • "Share URL" β†’ Confirm "Your app is live at https://${name}.exe.xyz - share this link!"
  • "Make changes" β†’ Acknowledge, stay ready for local edits
  • "Continue on VM" β†’ Provide: ssh ${name}.exe.dev -t "cd /var/www/html && claude"
  • "I'm done" β†’ Confirm app stays live, wish them well