exe
π―Skillfrom popmechanic/vibes-cli
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)
Installation
node deploy-exe.js --clerk-key "$(cat clerk-public-key.pem)" ...node deploy-exe.js --clerk-key "-----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----" ...Skill Details
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
- SSH key in
~/.ssh/(id_ed25519, id_rsa, or id_ecdsa) - exe.dev account - run
ssh exe.devonce to create your account and verify email - Generated Vibes app - an
index.htmlfile 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
- If AI enabled, ask for the OpenRouter API key
- If Registry enabled, ask for Clerk PEM public key and webhook secret
- 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
- Creates VM on exe.dev via SSH CLI
- Starts nginx (pre-installed on exeuntu image)
- Uploads your index.html to
/var/www/html/ - Generates HANDOFF.md - context document for remote Claude
- For Connect-enabled deployments, includes full Docker setup documentation (commands, troubleshooting, service endpoints)
- Makes VM public via
ssh exe.dev share set-public - 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:
- Installs Bun runtime
- Creates
/home/exedev/proxy.js- proxies/api/ai/*to OpenRouter - Configures systemd service for the proxy
- 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:
- Installs Bun runtime to
/usr/local/bin/bun - Creates
/var/www/registry-server.tswith Clerk JWT verification - Configures systemd service (port 3002)
- 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
- Go to Clerk Dashboard β API Keys
- Scroll to "PEM Public Key" or click "Show JWT Public Key"
- 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
- Add
--domainflag:
```bash
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:
```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
```
SSL Configuration
The deploy script preserves existing SSL by using include files for AI proxy config. When manually editing nginx:
- Never replace the entire config - only add/modify specific blocks
- Check for SSL first: Look for
listen 443 sslin the config - Use includes: Put new configs in
/etc/nginx/conf.d/or separate files - 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)= actual VM (for file operations, SSH access).exe.xyz
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 cpmoves file with correct ownership
Common mistakes:
| Mistake | Error | Fix |
|---------|-------|-----|
| ssh exe.dev cat /var/www/... | "No VMs found" | Use ssh |
| 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
# Read file
ssh
# Upload file (two-stage)
scp index.html
ssh
# Download file
scp
# Verify
ssh
```
---
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
More from this repository7
Vibes is the vibe coding web stack made for AI agents.
Generates React web apps with Fireproof, enabling local-first data sync and quick prototyping.
Transforms a Vibes app into a multi-tenant SaaS platform with subdomain-based tenancy, authentication, and subscription management.
Deploys a personal Fireproof Connect sync backend to a dedicated Studio VM on exe.dev, configuring cloud synchronization for Vibes apps.
Generates random music riffs and chord progressions using machine learning models for creative musical inspiration.
Generates design reference images by extracting and transforming visual elements from source images using AI-powered techniques.
Vibes is the vibe coding web stack made for AI agents.