🎯

sell

🎯Skill

from popmechanic/vibes-cli

VibeIndex|
What it does

Transforms a Vibes app into a multi-tenant SaaS platform with subdomain-based tenancy, authentication, and subscription management.

πŸ“¦

Part of

popmechanic/vibes-cli(8 items)

sell

Installation

Add MarketplaceAdd marketplace to Claude Code
/plugin marketplace add popmechanic/vibes-cli
Install PluginInstall plugin from marketplace
/plugin install vibes@vibes-cli
Claude CodeAdd plugin in Claude Code
/plugin update vibes@vibes-cli
git cloneClone repository
git clone https://github.com/popmechanic/vibes-cli.git ~/.vibes
Claude CodeAdd plugin in Claude Code
/plugin marketplace remove vibes-cli

+ 2 more commands

πŸ“– Extracted from docs: popmechanic/vibes-cli
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Transform a Vibes app into a multi-tenant SaaS with subdomain-based tenancy. Adds Clerk authentication, subscription gating, and generates a unified app with landing page, tenant routing, and admin dashboard.

Overview

Display this ASCII art immediately when starting:

```

β–‘β–’β–“β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–“β–’β–‘β–’β–“β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–“β–’β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘

β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘

β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘

β–‘β–’β–“β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–“β–’β–‘β–‘β–’β–“β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘

β–‘β–’β–“β–ˆβ–“β–’β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘

β–‘β–’β–“β–ˆβ–“β–’β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘ β–‘β–’β–“β–ˆβ–“β–’β–‘

β–‘β–’β–“β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–“β–’β–‘β–‘β–’β–“β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–“β–’β–‘β–’β–“β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–“β–’β–‘β–’β–“β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–“β–’β–‘

```

Quick Navigation

  • [Critical Rules](#-critical-rules---read-first-) - Read this first
  • [Step 1: Pre-Flight Checks](#step-1-pre-flight-checks) - Verify prerequisites
  • [Step 2: Clerk Configuration](#step-2-clerk-configuration-required) - Set up authentication (REQUIRED)
  • [Step 3: App Configuration](#step-3-app-configuration) - Collect app settings
  • [Step 4: Assembly](#step-4-assembly) - Build the unified app
  • [Step 5: Deployment](#step-5-deployment) - Go live with exe.dev
  • [Step 6: Post-Deploy Verification](#step-6-post-deploy-verification) - Confirm everything works
  • [Key Components](#key-components) - Routing, TenantContext, SubscriptionGate
  • [Troubleshooting](#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:

```bash

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-key and --clerk-webhook-secret

ALWAYS do these:

  • βœ… Complete pre-flight checks before starting
  • βœ… Collect ALL Clerk credentials BEFORE app configuration
  • βœ… Run assemble-sell.js to 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:

```bash

cat .env 2>/dev/null | grep VITE_API_URL || echo "NOT_FOUND"

```

If output shows NOT_FOUND:

> "Fireproof Connect is not configured. Run /vibes:connect first 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

```bash

ls -la app.jsx 2>/dev/null || echo "NOT_FOUND"

```

If output shows NOT_FOUND:

Check for riff directories:

```bash

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 to app.jsx
  • Found nothing β†’ Tell user to run /vibes:vibes first

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:

>

> 1. Create a Clerk Application at [clerk.com](https://clerk.com)

> - Choose "Email + Passkey" authentication

>

> 2. 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 |

>

> 3. 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 |

>

> 4. 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](./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:

```bash

cat > clerk-jwks-key.pem << 'EOF'

-----BEGIN PUBLIC KEY-----

[THE USER'S KEY CONTENT HERE]

-----END PUBLIC KEY-----

EOF

```

Verify the file was created:

```bash

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
for line breaks (e.g., 'SHARE YOUR DAY.
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

  1. If user selected "Custom domain", ask for the domain name
  2. Admin User IDs default to empty (configured after first deploy - see Step 6)
  3. 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.
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:

```bash

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:

```jsx

// 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:

  1. Find the useFireproofClerk("...") call
  2. Add const { dbName } = useTenant(); before it
  3. 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:

```bash

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.
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:

```bash

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 set
  • VITE_API_URL - must be set
  • VITE_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:

```css

: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:

```bash

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

```bash

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

```bash

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:

```bash

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-key and --clerk-webhook-secret were provided

---

Step 6: Post-Deploy Verification

6.1 Test Landing Page

```bash

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, or free

6.4 Admin Setup (After First Signup)

Guide the user through admin setup:

> Set Up Admin Access

>

> 1. Visit your app and sign up: https://wedding-photos.exe.xyz

> 2. Complete the signup flow (email β†’ verify β†’ passkey)

> 3. Go to Clerk Dashboard β†’ Users β†’ click your user β†’ copy User ID

> 4. Re-run assembly with admin access:

>

> ```bash

> 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 ...]

> ```

>

> 5. Re-deploy:

> ```bash

> 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:

```javascript

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:

```javascript

const TenantContext = createContext(null);

function TenantProvider({ children, subdomain }) {

const dbName = ${APP_NAME}-${subdomain};

return (

{children}

);

}

```

SubscriptionGate with Billing Mode

The subscription gate respects the billing mode setting:

  • off: Everyone gets free access after signing in
  • required: 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:

```json

{

"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