sell
npx skills add https://github.com/popmechanic/vibes-cli --skill sell
Agent 安装分布
Skill 文档
Display this ASCII art immediately when starting:
ââââââââââââââââââââââââââââââââ âââââââ
âââââââ âââââââ âââââââ âââââââ
âââââââ âââââââ âââââââ âââââââ
ââââââââââââââââââââââââ âââââââ âââââââ
âââââââââââââ âââââââ âââââââ
âââââââââââââ âââââââ âââââââ
âââââââââââââââââââââââââââââââââââââââââââââââââââââ
Quick Navigation
- Critical Rules – Read this first
- Step 1: Pre-Flight Checks – Verify prerequisites
- Step 2: Clerk Configuration – Set up authentication (REQUIRED)
- Step 3: App Configuration – Collect app settings
- Step 4: Assembly – Build the unified app
- Step 5: Deployment – Go live with exe.dev
- Step 6: Post-Deploy Verification – Confirm everything works
- Key Components – Routing, TenantContext, SubscriptionGate
- Troubleshooting – Common issues and fixes
â CRITICAL RULES – READ FIRST â
DO NOT generate code manually. This skill uses pre-built scripts:
| Step | Script | What it does |
|---|---|---|
| Assembly | assemble-sell.js |
Generates unified index.html |
| Deploy | deploy-exe.js |
Deploys to exe.dev with registry |
Script location:
node "${CLAUDE_PLUGIN_ROOT}/scripts/assemble-sell.js" ...
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" ...
NEVER do these manually:
- â Write HTML/JSX for landing page, tenant app, or admin dashboard
- â Generate routing logic or authentication code
- â Deploy without
--clerk-keyand--clerk-webhook-secret
ALWAYS do these:
- â Complete pre-flight checks before starting
- â Collect ALL Clerk credentials BEFORE app configuration
- â
Run
assemble-sell.jsto generate the unified app - â Deploy with ALL required flags
Sell – Transform Vibes to SaaS
This skill uses assemble-sell.js to inject the user’s app into a pre-built template. The template contains security checks, proper Clerk integration, and Fireproof patterns.
Convert your Vibes app into a multi-tenant SaaS product with:
- Subdomain-based tenancy (alice.yourdomain.com)
- Clerk authentication with passkeys
- Subscription gating via Clerk Billing
- Per-tenant Fireproof database isolation
- Marketing landing page
- Admin dashboard
Architecture
The sell skill generates a single index.html file that handles all routes via client-side subdomain detection:
yourdomain.com â Landing page
*.yourdomain.com â Tenant app with auth
admin.yourdomain.com â Admin dashboard
This approach simplifies deployment – you upload one file and it handles everything.
Step 1: Pre-Flight Checks
Before starting, verify these prerequisites. STOP if any check fails.
1.1 Check for Fireproof Connect
Run this command to check if Connect is configured:
cat .env 2>/dev/null | grep VITE_API_URL || echo "NOT_FOUND"
If output shows NOT_FOUND:
“Fireproof Connect is not configured. Run
/vibes:connectfirst to set up your sync backend, then return to/vibes:sell.”
STOP HERE if Connect is not configured. The sell skill requires cloud sync for multi-tenant data isolation.
1.2 Detect Existing App
ls -la app.jsx 2>/dev/null || echo "NOT_FOUND"
If output shows NOT_FOUND:
Check for riff directories:
ls -d riff-* 2>/dev/null
Decision tree:
- Found
app.jsxâ Proceed to Step 2 - Found multiple
riff-*/app.jsxâ Ask user to select one, then copy toapp.jsx - Found nothing â Tell user to run
/vibes:vibesfirst
STOP HERE if no app exists. The sell skill transforms existing apps.
1.3 Pre-Flight Summary
After both checks pass, confirm:
“Pre-flight checks passed:
- â Fireproof Connect configured (.env found)
- â App found (app.jsx)
Now let’s configure Clerk authentication. This is required for multi-tenant SaaS.”
Step 2: Clerk Configuration (REQUIRED)
These credentials are REQUIRED. Do not proceed without them.
2.1 Clerk Dashboard Setup Instructions
Before collecting credentials, the user must set up Clerk. Present these instructions:
Clerk Setup Required
Before we continue, you need to configure Clerk authentication:
Create a Clerk Application at clerk.com
- Choose “Email + Passkey” authentication
Configure Email Settings (Dashboard â User & Authentication â Email):
Setting Value Why Sign-up with email â ON Users sign up via email Require email address ⬠OFF CRITICAL – signup fails if ON Verify at sign-up â ON Verify before session Email verification code â Checked Use code for verification Configure Passkey Settings (Dashboard â User & Authentication â Passkeys):
Setting Value Sign-in with passkey â ON Allow autofill â ON Show passkey button â ON Add passkey to account â ON Create Webhook (Dashboard â Configure â Webhooks):
- Click “Add Endpoint”
- Enter URL:
https://YOUR-APP.exe.xyz/webhook(we’ll confirm the name later)- Select events:
user.created,user.deleted,subscription.*- Click “Create”
- Copy the “Signing Secret” (starts with
whsec_)See CLERK-SETUP.md for complete details.
When you’re ready, I’ll collect your Clerk credentials.
2.2 Collect Clerk Credentials
Use AskUserQuestion with these 3 questions:
Question 1: "What's your Clerk Publishable Key?"
Header: "Clerk Key"
Options: User enters via "Other"
Description: "From Clerk Dashboard â API Keys. Starts with pk_test_ or pk_live_"
Question 2: "What's your Clerk JWKS Public Key?"
Header: "JWKS Key"
Options: User enters via "Other"
Description: "From Clerk Dashboard â API Keys â scroll to 'Show JWT Public Key'. Starts with -----BEGIN PUBLIC KEY-----"
Question 3: "What's your Clerk Webhook Secret?"
Header: "Webhook"
Options: User enters via "Other"
Description: "From the webhook you created. Starts with whsec_"
2.3 Validation Gate
Before proceeding, validate ALL credentials:
| Credential | Valid Format | If Invalid |
|---|---|---|
| Publishable Key | Starts with pk_test_ or pk_live_ |
Stop, ask for correct key |
| JWKS Public Key | Starts with -----BEGIN PUBLIC KEY----- |
Stop, guide to JWT Public Key location |
| Webhook Secret | Starts with whsec_ |
Stop, guide to webhook creation |
If ANY validation fails: Stop and help user get the correct credential. Do not proceed to Step 3.
2.4 Save JWKS Key to File
After receiving and validating the JWKS key, save it to a file for the deploy command:
cat > clerk-jwks-key.pem << 'EOF'
-----BEGIN PUBLIC KEY-----
[THE USER'S KEY CONTENT HERE]
-----END PUBLIC KEY-----
EOF
Verify the file was created:
head -1 clerk-jwks-key.pem
Expected output: -----BEGIN PUBLIC KEY-----
2.5 Clerk Configuration Complete
Confirm to the user:
“Clerk credentials validated and saved:
- â Publishable Key: pk_test_… (saved for assembly)
- â JWKS Key: Saved to clerk-jwks-key.pem (for deployment)
- â Webhook Secret: whsec_… (saved for deployment)
Now let’s configure your app settings.”
Step 3: App Configuration
Use AskUserQuestion to collect all config in 2 batches.
Batch 1: Core Identity
Use the AskUserQuestion tool with these 4 questions:
Question 1: "What should we call this app?"
Header: "App Name"
Options: Provide 2 suggestions based on context + user enters via "Other"
Description: "Used for database naming and deployment URL (e.g., 'wedding-photos')"
Question 2: "What domain will this deploy to?"
Header: "Domain"
Options: ["Use exe.xyz subdomain", "Custom domain"]
Description: "exe.xyz is instant. Custom domains require DNS setup."
Question 3: "Do you want to require paid subscriptions?"
Header: "Billing"
Options: ["No - free access for all", "Yes - subscription required"]
Description: "Billing is configured in Clerk Dashboard â Billing"
Question 4: "Display title for your app?"
Header: "Title"
Options: Suggest based on app name + user enters via "Other"
Description: "Shown in headers and landing page"
Batch 2: Customization
Use the AskUserQuestion tool with these 3 questions:
Question 1: "Tagline for the landing page headline?"
Header: "Tagline"
Options: Generate 2 suggestions based on app context + user enters via "Other"
Description: "Bold headline text. Can include <br> for line breaks (e.g., 'SHARE YOUR DAY.<br>MAKE IT SPECIAL.')"
Question 2: "Subtitle text below the tagline?"
Header: "Subtitle"
Options: Generate 2 suggestions based on app context + user enters via "Other"
Description: "Explanatory text below the headline (e.g., 'The easiest way to share wedding photos with guests.')"
Question 3: "What features should we highlight on the landing page?"
Header: "Features"
Options: User enters via "Other"
Description: "Comma-separated list (e.g., 'Photo sharing, Guest uploads, Live gallery')"
After Receiving Answers
- If user selected “Custom domain”, ask for the domain name
- Admin User IDs default to empty (configured after first deploy – see Step 6)
- Proceed immediately to Step 4 (Assembly)
Config Values Reference
| Config | Script Flag | Example |
|---|---|---|
| App Name | --app-name |
wedding-photos |
| Domain | --domain |
myapp.exe.xyz |
| Billing | --billing-mode |
off or required |
| Clerk Publishable Key | --clerk-key |
pk_test_xxx |
| Title | --app-title |
Wedding Photos |
| Tagline | --tagline |
SHARE YOUR DAY.<br>MAKE IT SPECIAL. |
| Subtitle | --subtitle |
The easiest way to share wedding photos with guests. |
| Features | --features |
'["Feature 1","Feature 2"]' |
| Admin IDs | --admin-ids |
'["user_xxx"]' (default: '[]') |
Step 4: Assembly
CRITICAL: You MUST use the assembly script. Do NOT generate your own HTML/JSX code.
4.1 Verify .env Exists
Before running assembly, verify the .env file exists:
test -f .env && echo "OK" || echo "MISSING"
If MISSING: Stop and run /vibes:connect first.
4.2 Update App for Tenant Context
The user’s app needs to use useTenant() for database scoping. Check if their app has a hardcoded database name:
// BEFORE: Hardcoded name
const { useLiveQuery } = useFireproofClerk("my-app");
// AFTER: Tenant-aware
const { dbName } = useTenant();
const { useLiveQuery } = useFireproofClerk(dbName);
If the app uses a hardcoded name, update it:
- Find the
useFireproofClerk("...")call - Add
const { dbName } = useTenant();before it - Change to
useFireproofClerk(dbName)
The template makes useTenant available globally via window.useTenant.
4.3 Run Assembly Script
Run the assembly script with all collected values:
node "${CLAUDE_PLUGIN_ROOT}/scripts/assemble-sell.js" app.jsx index.html \
--clerk-key "pk_test_xxx" \
--app-name "wedding-photos" \
--app-title "Wedding Photos" \
--domain "myapp.exe.xyz" \
--tagline "SHARE YOUR DAY.<br>MAKE IT SPECIAL." \
--subtitle "The easiest way to share wedding photos with guests." \
--billing-mode "off" \
--features '["Photo sharing","Guest uploads","Live gallery"]' \
--admin-ids '[]'
4.4 Validation Gate: Check for Placeholders
After assembly, verify no config placeholders remain:
grep -o '__VITE_[A-Z_]*__' index.html | sort -u || echo "NO_PLACEHOLDERS"
If any placeholders found: The .env file is missing required values. Check:
VITE_CLERK_PUBLISHABLE_KEY– must be setVITE_API_URL– must be setVITE_CLOUD_URL– optional but recommended
Fix the .env file and re-run assembly.
4.5 Customize Landing Page Theme (Optional)
The template uses neutral colors by default. To match the user’s brand:
:root {
--landing-accent: #0f172a; /* Primary button/text color */
--landing-accent-hover: #1e293b; /* Hover state */
}
Examples based on prompt style:
- Wedding app â
--landing-accent: #d4a574;(warm gold) - Tech startup â
--landing-accent: #6366f1;(vibrant indigo) - Health/wellness â
--landing-accent: #10b981;(fresh green)
Step 5: Deployment
Registry server credentials are REQUIRED for SaaS apps.
5.0 Pre-Deploy Check: Verify JWKS Key File
Before deploying, verify the JWKS key file exists:
test -f clerk-jwks-key.pem && echo "â JWKS key file exists" || echo "â MISSING: clerk-jwks-key.pem"
If output shows MISSING:
Return to Step 2.4 and save the JWKS key to
clerk-jwks-key.pem
5.1 Deploy with ALL Required Flags
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" \
--name wedding-photos \
--file index.html \
--clerk-key "$(cat clerk-jwks-key.pem)" \
--clerk-webhook-secret "whsec_xxx"
Required Flags for SaaS:
| Flag | Source | Purpose |
|---|---|---|
--clerk-key |
clerk-jwks-key.pem file | Registry JWT verification |
--clerk-webhook-secret |
Clerk webhook | Subscription sync |
Without these flags, the registry server will NOT be deployed and subdomain claiming will NOT work.
5.2 DNS Configuration (For Custom Domains)
If using a custom domain (not just yourapp.exe.xyz), configure DNS:
| Type | Name | Value |
|---|---|---|
| ALIAS | @ | exe.xyz |
| CNAME | * | yourapp.exe.xyz |
Example for cosmic-garden.exe.xyz with custom domain cosmicgarden.app:
| Type | Name | Value |
|---|---|---|
| ALIAS | @ | exe.xyz |
| CNAME | * | cosmic-garden.exe.xyz |
This routes both the apex domain and all subdomains through exe.dev’s proxy, which handles SSL automatically.
Note: If your DNS provider doesn’t support ALIAS records, use the ?subdomain= query parameter as a fallback.
5.3 Optional: AI Features
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" \
--name wedding-photos \
--file index.html \
--clerk-key "$(cat clerk-jwks-key.pem)" \
--clerk-webhook-secret "whsec_xxx" \
--ai-key "sk-or-v1-your-provisioning-key" \
--multi-tenant \
--tenant-limit 5
5.4 Validation Gate: Verify Registry
After deployment, verify the registry server is running:
curl -s https://wedding-photos.exe.xyz/registry.json | head -c 100
Expected output: {"claims":{},"quotas":{},"reserved":["admin","api","www"]...
If you see HTML instead of JSON:
- The registry server isn’t running
- Check deployment logs
- Verify
--clerk-keyand--clerk-webhook-secretwere provided
Step 6: Post-Deploy Verification
6.1 Test Landing Page
curl -s -o /dev/null -w "%{http_code}" https://wedding-photos.exe.xyz
Expected: 200
6.2 Test Tenant Routing
Open in browser: https://wedding-photos.exe.xyz?subdomain=test
Should show the tenant app (may require sign-in).
6.3 Clerk Dashboard Checklist
Present this checklist to the user:
Clerk Dashboard Settings Checklist
Verify these settings in your Clerk Dashboard:
Domains (Dashboard â Domains):
- Add your deployment domain (e.g.,
wedding-photos.exe.xyz)- If using custom domain, add that too
Webhook (Dashboard â Configure â Webhooks):
- Endpoint URL matches your deployment:
https://wedding-photos.exe.xyz/webhook- Events selected:
user.created,user.deleted,subscription.*If using billing (Dashboard â Billing):
- Stripe connected
- Plans created with matching names:
pro,basic,monthly,yearly,starter, orfree
6.4 Admin Setup (After First Signup)
Guide the user through admin setup:
Set Up Admin Access
- Visit your app and sign up:
https://wedding-photos.exe.xyz- Complete the signup flow (email â verify â passkey)
- Go to Clerk Dashboard â Users â click your user â copy User ID
- Re-run assembly with admin access:
node "${CLAUDE_PLUGIN_ROOT}/scripts/assemble-sell.js" app.jsx index.html \ --clerk-key "pk_test_xxx" \ --app-name "wedding-photos" \ --app-title "Wedding Photos" \ --domain "wedding-photos.exe.xyz" \ --admin-ids '["user_xxx"]' \ [... other options ...]
- Re-deploy:
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" \ --name wedding-photos \ --file index.html \ --clerk-key "$(cat clerk-jwks-key.pem)" \ --clerk-webhook-secret "whsec_xxx"
Key Components
Client-Side Routing
The unified template uses getRouteInfo() to detect subdomain and route:
function getRouteInfo() {
const hostname = window.location.hostname;
const parts = hostname.split('.');
const params = new URLSearchParams(window.location.search);
const testSubdomain = params.get('subdomain');
// Handle localhost testing with ?subdomain= param
if (hostname === 'localhost' || hostname === '127.0.0.1') {
if (testSubdomain === 'admin') return { route: 'admin', subdomain: null };
if (testSubdomain) return { route: 'tenant', subdomain: testSubdomain };
return { route: 'landing', subdomain: null };
}
// Handle exe.xyz testing (before custom domain is set up)
if (hostname.endsWith('.exe.xyz')) {
if (testSubdomain === 'admin') return { route: 'admin', subdomain: null };
if (testSubdomain) return { route: 'tenant', subdomain: testSubdomain };
return { route: 'landing', subdomain: null };
}
// Production: detect subdomain from hostname
if (parts.length <= 2 || parts[0] === 'www') {
return { route: 'landing', subdomain: null };
}
if (parts[0] === 'admin') {
return { route: 'admin', subdomain: null };
}
return { route: 'tenant', subdomain: parts[0] };
}
TenantContext
Provides database scoping for tenant apps:
const TenantContext = createContext(null);
function TenantProvider({ children, subdomain }) {
const dbName = `${APP_NAME}-${subdomain}`;
return (
<TenantContext.Provider value={{ subdomain, dbName, appName: APP_NAME, domain: APP_DOMAIN }}>
{children}
</TenantContext.Provider>
);
}
SubscriptionGate with Billing Mode
The subscription gate respects the billing mode setting:
off: Everyone gets free access after signing inrequired: Users must subscribe via Clerk Billing
Admins always bypass the subscription check.
SECURITY WARNING: Do NOT add fallbacks like || ADMIN_USER_IDS.length === 0 to admin checks. An empty admin list means NO admin access, not “everyone is admin”.
Testing
Test different routes by adding ?subdomain= parameter:
Localhost:
http://localhost:5500/index.html â Landing page
http://localhost:5500/index.html?subdomain=test â Tenant app
http://localhost:5500/index.html?subdomain=admin â Admin dashboard
exe.xyz (before custom domain):
https://myapp.exe.xyz â Landing page
https://myapp.exe.xyz?subdomain=test â Tenant app
https://myapp.exe.xyz?subdomain=admin â Admin dashboard
Import Map
The unified template uses React 19 with @necrodome/fireproof-clerk for Clerk integration:
{
"imports": {
"react": "https://esm.sh/stable/react@19.2.4",
"react/jsx-runtime": "https://esm.sh/stable/react@19.2.4/jsx-runtime",
"react/jsx-dev-runtime": "https://esm.sh/stable/react@19.2.4/jsx-dev-runtime",
"react-dom": "https://esm.sh/stable/react-dom@19.2.4",
"react-dom/client": "https://esm.sh/stable/react-dom@19.2.4/client",
"use-fireproof": "https://esm.sh/stable/@necrodome/fireproof-clerk@0.0.3?external=react,react-dom",
"@fireproof/clerk": "https://esm.sh/stable/@necrodome/fireproof-clerk@0.0.3?external=react,react-dom"
}
}
Troubleshooting
“Unexpected token ‘<'” in console
- JSX not being transpiled by Babel
- Check that
<script type="text/babel" data-type="module">is present
“Cannot read properties of null (reading ‘useEffect’)”
- React version mismatch between packages
- Ensure fireproof-clerk imports have
?external=react,react-dom
“Subscription Required” loop
- Check that admin user ID is correct and in the
ADMIN_USER_IDSarray - Verify Clerk Billing is set up with matching plan names
Clerk not loading
- Add your domain to Clerk’s authorized domains
- Check publishable key is correct (not secret key)
Admin shows “Access Denied”
- User ID not in –admin-ids array
- Check Clerk Dashboard â Users â click user â copy User ID
- Re-run assembly with correct –admin-ids
Database not isolated
- Verify
useTenant()is used in the App component - Check
useFireproofClerk(dbName)uses the tenant database name
Passkey creation fails
- Ensure HTTPS is configured (passkeys require secure context)
- For production: use
pk_live_*key and add domain to allowed origins
“Verification incomplete (missing_requirements)” error
- Go to Clerk Dashboard â User & Authentication â Email
- Set “Require email address” to OFF (critical fix!)
- Ensure “Sign-up with email” is ON
- Ensure “Verify at sign-up” is ON with “Email verification code” checked
Registry returns HTML instead of JSON
- The registry server isn’t running
- Deploy was run without
--clerk-keyand--clerk-webhook-secret - Re-deploy with both flags:
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-exe.js" \ --name myapp \ --file index.html \ --clerk-key "$(cat clerk-jwks-key.pem)" \ --clerk-webhook-secret "whsec_xxx"
Assembly fails with “.env file not found”
- Fireproof Connect is not configured
- Run
/vibes:connectfirst to set up your sync backend - Then return to
/vibes:sell
What’s Next?
After Step 6 verification completes, present options:
Question: "Your SaaS is deployed and verified! What would you like to do?"
Header: "Next"
Options:
- Label: "Set up admin access (Recommended)"
Description: "Sign up on your app, get your user ID, and enable admin dashboard access."
- Label: "Customize landing page"
Description: "Adjust colors, refine tagline, or update feature descriptions."
- Label: "I'm done for now"
Description: "Your app is live at https://yourapp.exe.xyz"