VibesOS sell
Self-contained SaaS automation — invoke directly, do not decompose. Transforms a Vibes app into a multi-tenant SaaS with subdomain-based tenancy. Adds Pocket ID authentication, subscription gating, and generates a unified app with landing page, tenant routing, and admin dashboard. Use when the user wants to monetize an app, add auth and billing, create a SaaS product, add subscriptions, or turn an app into a business.
git clone https://github.com/popmechanic/VibesOS
T=$(mktemp -d) && git clone --depth=1 https://github.com/popmechanic/VibesOS "$T" && mkdir -p ~/.claude/skills && cp -r "$T/vibes-desktop/build/stable-macos-arm64/VibesOS.app/Contents/Resources/vibes-plugin/skills/sell" ~/.claude/skills/popmechanic-vibesos-sell && rm -rf "$T"
vibes-desktop/build/stable-macos-arm64/VibesOS.app/Contents/Resources/vibes-plugin/skills/sell/SKILL.mdPlan mode: If you are planning work, this entire skill is ONE plan step: "Invoke /vibes:sell". Do not decompose the steps below into separate plan tasks.
Display this ASCII art immediately when starting:
░▒▓███████▓▒░▒▓████████▓▒░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓██████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓████████▓▒░▒▓████████▓▒░▒▓████████▓▒░
Quick Navigation
- Critical Rules - Read this first
- Step 1: Pre-Flight Checks - Verify prerequisites
- Step 2: App Identity - Set up app name and deploy URL
- Step 3: App Configuration - Collect app settings
- Step 4: Assembly - Build the unified app
- Step 5: Deployment - Deploy to Cloudflare Workers
- Step 6: Post-Deploy Verification - Confirm everything works
- Key Components - Routing, TenantContext, SubscriptionGate
- Troubleshooting - Common issues and fixes
Assembly: transform (strip) —
receives a vibes-generated app.jsx and adapts it for the sell template. It stripsassemble-sell.jsstatements,import, React destructuring, and template constants — because the sell template already provides all of these. All dependencies (export default, TinyBase hooks,React,useTenant, etc.) are available as globals.useState
⛔ CRITICAL RULES - READ FIRST ⛔
DO NOT generate code manually. This skill uses pre-built scripts:
| Step | Script | What it does |
|---|---|---|
| Assembly | | Generates unified index.html |
| Deploy | | Deploys to Cloudflare Workers with registry |
Script location:
VIBES_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "${CLAUDE_SKILL_DIR}")")}" bun "$VIBES_ROOT/scripts/assemble-sell.js" ... bun "$VIBES_ROOT/scripts/deploy-cloudflare.js" ...
NEVER do these manually:
- ❌ Write HTML/JSX for landing page, tenant app, or admin dashboard
- ❌ Generate routing logic or authentication code
ALWAYS do these:
- ✅ Complete pre-flight checks before starting
- ✅ Run
to generate the unified appassemble-sell.js - ✅ Deploy with
deploy-cloudflare.js
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, Pocket ID auth integration, and TinyBase data patterns.
Convert your Vibes app into a multi-tenant SaaS product with:
- Subdomain-based tenancy (alice.yourdomain.com)
- Pocket ID authentication (with passkeys, automatic on deploy)
- Subscription gating (Stripe billing is phase 2)
- Per-tenant data isolation via TinyBase rooms (Durable Objects)
- 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.
Terminal or Editor UI?
Detect whether you're running in a terminal (Claude Code CLI) or an editor (Cursor, Windsurf, VS Code with Copilot). Terminal agents use
AskUserQuestion for all input. Editor agents present requirements as a checklist comment, wait for user edits, then proceed. See the vibes skill for the full detection and interaction pattern.
Step 1: Pre-Flight Checks
Before starting, verify these prerequisites. STOP if any check fails.
1.1 Auth Check
Auth is automatic — on first deploy, a browser window opens for Pocket ID login. Tokens are cached at
~/.vibes/auth.json for subsequent deploys. No .env credential setup is needed.
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
→ Proceed to Step 2app.jsx - Found multiple
→ Ask user to select one, then copy toriff-*/app.jsxapp.jsx - Found nothing → Tell user to run
first/vibes:vibes
STOP HERE if no app exists. The sell skill transforms existing apps.
1.3 Pre-Flight Summary
After checks pass, confirm:
"Pre-flight checks passed:
- ✓ App found (app.jsx)
- ✓ Auth is automatic via Pocket ID (browser login on first deploy)
Now let's configure your app settings."
Step 2: App Identity
2.1 Collect App Name (Needed for Deploy URL)
Collect the app name for deployment.
Use AskUserQuestion:
Question: "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')" multiSelect: false
Store as
appName (URL-safe slug: lowercase, hyphens, no special chars).
The app will be deployed via the Deploy API (
/vibes:cloudflare), which assigns the domain automatically. Store {appName}.vibes.diy as domain.
Step 3: App Configuration
Use AskUserQuestion to collect all config in 2 batches.
Batch 1: Core Identity
App name and deploy domain were already resolved in Step 2.2. Custom domains can be configured later (Step 5.2).
Use the AskUserQuestion tool with these 2 questions:
Question 1: "Do you want to require paid subscriptions?" Header: "Billing" Options: ["No - free access for all", "Yes - subscription required"] Description: "Billing via Stripe is planned for phase 2. Choose 'No' for now unless you have a custom Stripe integration." Question 2: "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
When billing is enabled (
billingMode === "required"): These fields appear on a pricing section visible to potential customers before signup. Write them as marketing copy — benefit-driven, not technical.
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.'). When billing is on, this is the sales headline — make it benefit-driven." 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.'). When billing is on, this is the value proposition — answer 'why should I pay?'" 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'). When billing is on, these appear as a visual checklist on the pricing section. Each should be a compelling benefit statement, not technical jargon. Aim for 3-5 items."
After Receiving Answers
- Domain is
(resolved in Step 2.2). Custom domains can be added post-deploy (Step 5.2).{domain} - 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 | | |
| Domain | | |
| Billing | | or |
| Title | | |
| Tagline | | |
| Subtitle | | |
| Features | | |
| Admin IDs | | (default: ) |
Step 4: Assembly
CRITICAL: You MUST use the assembly script. Do NOT generate your own HTML/JSX code.
4.1 Auth Note
Auth is automatic via Pocket ID — no
.env credential setup is needed. On first deploy, a browser window opens for login. Tokens are cached at ~/.vibes/auth.json.
4.2 Update App for Tenant Context
The user's app needs to use
useTenant() for tenant-scoped data. TinyBase uses a single store per app with rooms via Durable Objects for multi-tenant isolation — no database name parameter is needed.
// TinyBase hooks are globals — no initialization call needed. // useTenant() provides tenant context for routing/display. const { subdomain } = useTenant(); // Use TinyBase hooks directly: const rowIds = useRowIds('items');
useTenant() is a template global (injected by AppWrapper in the sell template), NOT an importable module. Call it directly — do NOT write import { useTenant } from ... anywhere in app.jsx.
Template-Provided Globals — do NOT redeclare these in app.jsx:
| Category | Globals |
|---|---|
| React | , , , , , , , |
| Template utilities | , , |
| UI components | , , , , , , |
| Color constants | , , , |
Do NOT destructure from React (e.g.,
const { useState } = React;) or import React hooks — they are already in scope from the template.
4.3 Run Assembly Script
Before running assembly, check the project
.env for a cached admin user ID:
grep ADMIN_USER_ID .env 2>/dev/null
If found, offer to include it (mask the middle, e.g.,
user_37ici...ohcY):
AskUserQuestion: Question: "Include stored admin user ID in this deploy? (user_37ici...ohcY)" Header: "Admin" Options: - Label: "Yes, include" Description: "Pass --admin-ids with the cached user ID" - Label: "No, skip admin" Description: "Deploy without admin access (can add later in Step 6)" - Label: "Enter different" Description: "I'll paste a different user ID"
If "Yes, include": pass
--admin-ids '["<user_id>"]'. If "Enter different": collect new ID, save to .env, then pass it. If "No, skip admin": pass --admin-ids '[]'.
If not found: use
--admin-ids '[]' (admin setup happens post-deploy in Step 6.4).
Run the assembly script with all collected values:
VIBES_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "${CLAUDE_SKILL_DIR}")")}" bun "$VIBES_ROOT/scripts/assemble-sell.js" app.jsx index.html \ --app-name "wedding-photos" \ --app-title "Wedding Photos" \ --domain "{domain}" \ --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: Re-run assembly with the correct flags. Auth credentials are managed automatically — no
.env setup needed.
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 →
(warm gold)--landing-accent: #d4a574; - Tech startup →
(vibrant indigo)--landing-accent: #6366f1; - Health/wellness →
(fresh green)--landing-accent: #10b981;
Step 5: Deployment
Deploy Target: Cloudflare Workers. SaaS apps always deploy to Cloudflare Workers. The KV registry and subdomain routing require the CF Worker runtime.
5.1 Deploy to Cloudflare Workers
VIBES_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "${CLAUDE_SKILL_DIR}")")}" bun "$VIBES_ROOT/scripts/deploy-cloudflare.js" \ --name wedding-photos \ --file index.html
On first deploy, a browser window opens for Pocket ID authentication. Tokens are cached at
~/.vibes/auth.json for subsequent deploys.
5.2 DNS Configuration (For Custom Domains)
The app is immediately available at
{appName}.{subdomain}.workers.dev. For a custom domain:
- In the Cloudflare dashboard, go to Workers & Pages → your worker → Settings → Domains & Routes
- Add a custom domain (e.g.,
)cosmicgarden.app - For wildcard subdomains (e.g.,
), add a wildcard route*.cosmicgarden.app
Note: Until a custom domain with wildcard SSL is configured, use the
?subdomain= query parameter for tenant routing (e.g., https://{domain}?subdomain=alice).
5.3 Optional: AI Features
VIBES_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "${CLAUDE_SKILL_DIR}")")}" bun "$VIBES_ROOT/scripts/deploy-cloudflare.js" \ --name wedding-photos \ --file index.html \ --ai-key "sk-or-v1-your-provisioning-key"
5.4 Validation Gate: Verify Registry
After deployment, verify the registry is working:
curl -s https://{domain}/registry.json | head -c 100
Expected output:
{"claims":{},"reserved":["admin","api","www"]...
If you see HTML instead of JSON:
- The Worker may not have deployed correctly
- Check
for errorsbunx wrangler tail --name {appName}
Step 6: Post-Deploy Verification
6.1 Test Landing Page
curl -s -o /dev/null -w "%{http_code}" https://{domain}
Expected:
200
6.2 Test Tenant Routing
Open in browser:
https://{domain}?subdomain=test
Should show the tenant app (may require sign-in).
6.3 Auth Verification Checklist
Present this checklist to the user:
Authentication Checklist
Verify these for your deployment:
Pocket ID Auth:
- Auth token cached at
(created on first deploy)~/.vibes/auth.json- Sign-in flow works on the deployed URL
If using custom domain:
- Add the custom domain as an allowed origin in Pocket ID
6.4 Billing Verification (if --billing-mode required
)
--billing-mode requiredNote: Stripe billing integration is planned for phase 2. For now, billing mode "required" gates access but Stripe checkout is not yet wired up. Verify the paywall UI appears correctly:
- Check landing page: Open
and confirm the landing page is visiblehttps://{domain} - Test auth gate: Open
, and confirm unauthenticated users see the auth screenhttps://{domain}?subdomain=test - Verify access: After signing in, confirm the user can access the tenant app
6.5 Admin Setup (After First Signup)
Guide the user through admin setup:
Set Up Admin Access
- Visit your app and sign up:
https://{domain}- Complete the signup flow (email or passkey via Pocket ID)
- Find your User ID from the Pocket ID admin panel or application logs
- Re-run assembly with admin access:
VIBES_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "${CLAUDE_SKILL_DIR}")")}" bun "$VIBES_ROOT/scripts/assemble-sell.js" app.jsx index.html \ --app-name "{appName}" \ --app-title "{appTitle}" \ --domain "{domain}" \ --admin-ids '["user_xxx"]' \ [... other options ...]
- Re-deploy:
VIBES_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "${CLAUDE_SKILL_DIR}")")}" bun "$VIBES_ROOT/scripts/deploy-cloudflare.js" \ --name {appName} \ --file index.html
After collecting the user ID, save it to the project
.env for reference:
grep -q ADMIN_USER_ID .env 2>/dev/null && \ sed -i '' 's/^ADMIN_USER_ID=.*/ADMIN_USER_ID=<new>/' .env || \ echo "ADMIN_USER_ID=<new>" >> .env
Key Components & Troubleshooting
For routing internals (getRouteInfo, TenantContext, SubscriptionGate), testing routes, import map details, and troubleshooting common errors, read
${CLAUDE_SKILL_DIR}/references/components-and-troubleshooting.md.
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://{domain}"