Claude-skill-registry exe
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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/exe" ~/.claude/skills/majiayu000-claude-skill-registry-exe && rm -rf "$T"
skills/data/exe/SKILL.mdDeploy 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
(id_ed25519, id_rsa, or id_ecdsa)~/.ssh/ - exe.dev account - run
once to create your account and verify emailssh exe.dev - Generated Vibes app - an
file ready to deployindex.html
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
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
- Makes VM public via
ssh exe.dev share set-public <vmname> - Verifies public access at
https://myapp.exe.xyz
AI-Enabled Apps
For 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:
- Installs Bun runtime
- Creates
- proxies/home/exedev/proxy.js
to OpenRouter/api/ai/* - 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:
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:
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
with Clerk JWT verification/var/www/registry-server.ts - Configures systemd service (port 3002)
- Adds nginx proxy for
,/registry.json
,/check/*
,/claim/webhook
Registry Endpoints
| Endpoint | Method | Auth | Description |
|---|---|---|---|
| GET | None | Public read of all claims |
| GET | None | Check availability |
| POST | Bearer JWT | Claim subdomain for user |
| 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
# 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
Continue Development on the VM
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.
Manual Public Access
If the deploy script doesn't make the VM public automatically, run:
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:
// 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
flag:--domainnode "${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"
CLI Options
| Option | Description |
|---|---|
| VM name (required) |
| HTML file to deploy (default: index.html) |
| Custom domain for wildcard setup |
| OpenRouter API key for AI features |
| Enable subdomain-based multi-tenancy |
| Credit limit per tenant in dollars (default: 5) |
| Clerk PEM public key for JWT verification |
| Clerk webhook signing secret |
| Comma-separated reserved subdomain names |
| Pre-claimed subdomains (format: ) |
| Show commands without executing |
| Skip deployment verification |
Redeployment
After making changes, redeploy with:
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" --name myapp
SSH Access
Access your VM directly:
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 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 | ❌ | ✅ |
| 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
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
in the configlisten 443 ssl - Use includes: Put new configs in
or separate files/etc/nginx/conf.d/ - Test before reload:
sudo nginx -t
exe.dev Home Directory
The 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/
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