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 Vibes apps to exe.dev VM hosting with nginx, HTTPS, and persistent storage. Use when user wants to deploy an index.html file to a live VM. Handles AI proxy setup, multi-tenant subdomain isolation, and SaaS registry servers with Clerk authentication.
/plugin marketplace add popmechanic/vibes-cli/plugin install vibes@vibes-cliThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Deploy your Vibes app to exe.dev, a VM hosting platform with persistent storage and HTTPS by default.
~/.ssh/ (id_ed25519, id_rsa, or id_ecdsa)ssh exe.dev once to create your account and verify emailindex.html file ready to deployUse 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"]
cd "${CLAUDE_PLUGIN_ROOT}/scripts" && [ -d node_modules ] || npm install
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp --file index.html
/var/www/html/ssh exe.dev share set-public <vmname>https://myapp.exe.xyzFor apps using the useAI hook, deploy with the --ai-key flag:
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:
/home/exedev/proxy.js - proxies /api/ai/* to OpenRouterIMPORTANT: 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.
For SaaS apps with per-tenant AI:
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp --file index.html --ai-key "sk-or-v1-..." --multi-tenant
For SaaS apps using subdomain claiming (from /vibes:sell), deploy with Clerk credentials:
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:
/usr/local/bin/bun/var/www/registry-server.ts with Clerk JWT verification/registry.json, /check/*, /claim, /webhook| 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 |
The registry server needs Clerk's PEM public key to verify JWTs for the /claim endpoint.
Option 1: From Clerk Dashboard
-----BEGIN PUBLIC KEY-----)Option 2: From JWKS endpoint
# 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:
# 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:
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
Claude is pre-installed on exe.dev VMs. After deployment, you can continue development remotely:
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.
If the deploy script doesn't make the VM public automatically, run:
ssh exe.dev share set-public myapp
For apps that need tenant isolation (e.g., alice.myapp.com, bob.myapp.com):
The same index.html serves all subdomains. JavaScript reads the hostname and uses the subdomain as a Fireproof database prefix:
// 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);
Add --domain flag:
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp --domain myapp.com
Configure wildcard DNS at your DNS provider:
*.myapp.com CNAME myapp.exe.xyz
myapp.com ALIAS exe.xyz
Set up wildcard SSL on the VM:
ssh myapp.exe.dev
sudo apt install certbot
sudo certbot certonly --manual --preferred-challenges dns \
-d "myapp.com" -d "*.myapp.com"
| Option | Description |
|---|---|
--name <vm> | VM name (required) |
--file <path> | HTML file to deploy (default: index.html) |
--domain <domain> | Custom domain for wildcard setup |
--ai-key <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 <pem> | Clerk PEM public key for JWT verification |
--clerk-webhook-secret <secret> | Clerk webhook signing secret |
--reserved <list> | Comma-separated reserved subdomain names |
--preallocated <list> | Pre-claimed subdomains (format: sub:user_id) |
--dry-run | Show commands without executing |
--skip-verify | Skip deployment verification |
After making changes, redeploy with:
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp
Access your VM directly:
ssh myapp.exe.dev
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
| Service | Port | Purpose |
|---|---|---|
| AI Proxy | 3001 | OpenRouter proxy for useAI hook |
| Registry | 3002 | Subdomain claim/check API |
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:
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name <vmname> --file index.html
The deploy script preserves existing SSL by using include files for AI proxy config. When manually editing nginx:
listen 443 ssl in the config/etc/nginx/conf.d/ or separate filessudo nginx -tThe home directory on exe.dev VMs is /home/exedev (not ~ expansion). For manual file operations:
# Use explicit path or /tmp for staging
scp file.html vmname.exe.xyz:/home/exedev/
# or
scp file.html vmname.exe.xyz:/tmp/
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:
ssh ${name}.exe.dev -t "cd /var/www/html && claude"This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.