Loading...
Loading...
Self-contained app generator — invoke this skill directly, do not decompose into sub-steps. Generates React web apps with Fireproof database. Use when creating new web applications, adding components, or working with local-first databases. Ideal for quick prototypes and single-page apps that need real-time data sync.
npx skill4agent add popmechanic/vibes-cli vibesPlan mode: If you are planning work, this entire skill is ONE plan step: "Invoke /vibes:vibes". Do not decompose the steps below into separate plan tasks.
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░░▒▓███████▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓██████▓▒░
░▒▓█▓▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓██▓▒░ ░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░if test -f "./.env" && \
grep -qE "^VITE_CLERK_PUBLISHABLE_KEY=pk_(test|live)_" ./.env 2>/dev/null && \
grep -qE "^VITE_API_URL=" ./.env 2>/dev/null && \
grep -qE "^VITE_CLOUD_URL=" ./.env 2>/dev/null; then
echo "CONNECT_READY"
else
echo "CONNECT_NOT_READY"
fiConnect with Clerk authentication is required for Vibes apps.
/vibes:connectuse-fireproof/fireproof-vibes-bridge.jsimport { useFireproofClerk } from "use-fireproof"./fireproof-clerk-bundle.jsuseLiveQuerySyncStatusDotuseFireproofClerk<design><design>
- What is the core functionality and user flow?
- What OKLCH colors fit this theme? (dark/light, warm/cool, vibrant/muted)
- What layout best serves the content? (cards, list, dashboard, single-focus)
- What micro-interactions would feel satisfying? (hover states, transitions)
- What visual style matches the purpose? (minimal, bold, playful, professional)
</design>Assembly: generate (preserve) —injects your code as-is. Import and export statements work because the import map intercepts bare specifiers at runtime. Code examples below include imports.assemble.jsIf you're a launch/builder agent: Sell transforms vibes artifacts by stripping imports. When generating app.jsx for the launch pipeline, omit all imports — the sell template provides everything. Follow builder.md rules; use only the patterns from examples below, not the import lines.
<code><code>
import React, { useState } from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("app-name-db");
// ... component logic
return (
<div className="min-h-screen bg-[#f1f5f9] p-4">
{/* Sync status indicator (optional) */}
<div className="text-xs text-gray-500 mb-2">Sync: {syncStatus}</div>
{/* Your app UI */}
</div>
);
}
</code>@necrodome/fireproof-clerkuseFireproofClerk// ✅ CORRECT - This is the ONLY pattern that works
import { useFireproofClerk } from "use-fireproof";
const { database, useDocument, useLiveQuery, syncStatus } = useFireproofClerk("my-db");
const { doc, merge } = useDocument({ _id: "doc1" });
// ❌ WRONG - DO NOT USE (old use-vibes API)
import { toCloud, useFireproof } from "use-fireproof"; // WRONG - old API
import { useDocument } from "use-fireproof"; // WRONG - standalone import
const { attach } = useFireproof("db", { attach: toCloud() }); // WRONG - old patternsyncStatus"idle""connecting""synced""reconnecting""error"assemble.jswindow.__VIBES_CONFIG__.env<code>app.jsx<design>design.mdnode "${CLAUDE_PLUGIN_ROOT}/scripts/assemble.js" app.jsx index.html⚠️ DEPRECATED API: Never use the oldwithuseFireproofpattern. See references/DEPRECATED.md for migration details if you encounter legacy code.toCloud()
oklch(L C H)
/* L = Lightness (0-1): 0 black, 1 white */
/* C = Chroma (0-0.4): 0 gray, higher = more saturated */
/* H = Hue (0-360): color wheel degrees */{/* Dark/moody theme */}
className="bg-[oklch(0.15_0.02_250)]" /* Deep blue-black */
{/* Warm/cozy theme */}
className="bg-[oklch(0.25_0.08_30)]" /* Warm brown */
{/* Fresh/bright theme */}
className="bg-[oklch(0.95_0.03_150)]" /* Mint white */
{/* Vibrant accent */}
className="bg-[oklch(0.7_0.2_145)]" /* Vivid green */in oklch{/* Smooth gradient - no gray middle */}
className="bg-[linear-gradient(in_oklch,oklch(0.6_0.2_250),oklch(0.6_0.2_150))]"
{/* Sunset gradient */}
className="bg-[linear-gradient(135deg_in_oklch,oklch(0.7_0.25_30),oklch(0.5_0.2_330))]"
{/* Dark glass effect */}
className="bg-[linear-gradient(180deg_in_oklch,oklch(0.2_0.05_270),oklch(0.1_0.02_250))]"border-[#0f172a]shadow-[6px_6px_0px_#0f172a]<button className="px-6 py-3 bg-[oklch(0.95_0.02_90)] border-4 border-[#0f172a] shadow-[6px_6px_0px_#0f172a] hover:shadow-[4px_4px_0px_#0f172a] font-bold">
Click Me
</button><div className="bg-white/5 backdrop-blur-lg border border-white/10 rounded-2xl">
{/* content */}
</div>import { useFireproofClerk } from "use-fireproof";
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("my-app-db");ClerkFireproofProvideruseFireproofClerkmerge()submit()save()// FORM PATTERN: User types, then submits
const { doc, merge, submit } = useDocument({ title: "", body: "", type: "post" });
// merge({ title: "..." }) on each keystroke, submit() when done
// IMMEDIATE PATTERN: Each click is a complete action
const { docs } = useLiveQuery("_id", { key: "counter" });
const count = docs[0]?.value || 0;
const increment = () => database.put({ _id: "counter", value: count + 1 });merge()Date.now()crypto.randomUUID()submit()submit()database.put()// BAD — ts may not be saved due to React batching
merge({ ts: Date.now() });
submit();
// GOOD — all fields written atomically
await database.put({ text: doc.text, ts: Date.now(), type: "item" });
reset();useState()merge()submit()useDocumentuseState// Create new documents (auto-generated _id recommended)
const { doc, merge, submit, reset } = useDocument({ text: "", type: "item" });
// Edit existing document by known _id
const { doc, merge, save } = useDocument({ _id: "user-profile:abc@example.com" });
// Methods:
// - merge(updates) - update fields: merge({ text: "new value" })
// - submit(e) - save + reset (for forms creating new items)
// - save() - save without reset (for editing existing items)
// - reset() - discard changes// Simple: query by field value
const { docs } = useLiveQuery("type", { key: "item" });
// Recent items (_id is roughly temporal - great for simple sorting)
const { docs } = useLiveQuery("_id", { descending: true, limit: 100 });
// Range query
const { docs } = useLiveQuery("rating", { range: [3, 5] });// GOOD: Query all, filter in render
const { docs: allItems } = useLiveQuery("type", { key: "item" });
const filtered = allItems.filter(d => d.category === selectedCategory);// Create/update
const { id } = await database.put({ text: "hello", type: "item" });
// Delete
await database.del(item._id);import React from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("my-db");
// Form for new items (submit resets for next entry)
const { doc, merge, submit } = useDocument({ text: "", type: "item" });
// Live list of all items of type "item"
const { docs } = useLiveQuery("type", { key: "item" });
return (
<div className="min-h-screen bg-[#f1f5f9] p-4">
{/* Optional sync status indicator */}
<div className="text-xs text-gray-500 mb-2">Sync: {syncStatus}</div>
<form onSubmit={submit} className="mb-4">
<input
value={doc.text}
onChange={(e) => merge({ text: e.target.value })}
className="w-full px-4 py-3 border-4 border-[#0f172a]"
/>
<button type="submit" className="mt-2 px-4 py-2 bg-[#0f172a] text-[#f1f5f9]">
Add
</button>
</form>
{docs.map(item => (
<div key={item._id} className="p-2 mb-2 bg-white border-4 border-[#0f172a]">
{item.text}
<button onClick={() => database.del(item._id)} className="ml-2 text-red-500">
Delete
</button>
</div>
))}
</div>
);
}useAIThis app needs AI capabilities. Please provide your OpenRouter API key. Get one at: https://openrouter.ai/keys
--ai-keyuseAIimport React from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, syncStatus } = useFireproofClerk("ai-chat-db");
const { callAI, loading, error } = useAI();
const handleSend = async (message) => {
// Save user message
await database.put({ role: "user", content: message, type: "message" });
// Call AI
const response = await callAI({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: message }]
});
// Save AI response
const aiMessage = response.choices[0].message.content;
await database.put({ role: "assistant", content: aiMessage, type: "message" });
};
// Handle limit exceeded
if (error?.code === 'LIMIT_EXCEEDED') {
return (
<div className="p-4 bg-amber-100 text-amber-800 rounded">
AI usage limit reached. Please wait for monthly reset or upgrade your plan.
</div>
);
}
// ... rest of UI
}const { callAI, loading, error, clearError } = useAI();
// callAI options
await callAI({
model: "anthropic/claude-sonnet-4", // or other OpenRouter models
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" }
],
temperature: 0.7, // optional
max_tokens: 1000 // optional
});
// error structure
error = {
code: "LIMIT_EXCEEDED" | "API_ERROR" | "NETWORK_ERROR",
message: "Human-readable error message"
}node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-cloudflare.js" \
--name myapp \
--file index.html \
--ai-key "sk-or-v1-your-key"useSharingconst { inviteUser, listInvites, deleteInvite, findUser, ready } = window.useSharing();
// Invite by email
async function handleInvite(email) {
if (!ready) return;
const result = await inviteUser(email, 'read'); // 'read' or 'write'
console.log('Invited:', result);
}window.useSharingreadyuseStateuseDocumentFireproof.fireproof()useFireproofClerk()useFireprooftoCloud()useFireproofClerkcall-aiuseAI_files// Convert file to Uint8Array (with resize)
async function fileToImageData(file, maxDim = 1200) {
const bitmap = await createImageBitmap(file);
const scale = Math.min(1, maxDim / Math.max(bitmap.width, bitmap.height));
const canvas = new OffscreenCanvas(bitmap.width * scale, bitmap.height * scale);
canvas.getContext('2d').drawImage(bitmap, 0, 0, canvas.width, canvas.height);
const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality: 0.8 });
return new Uint8Array(await blob.arrayBuffer());
}
// Display from Uint8Array
function StoredImage({ data, type = 'image/jpeg', alt, className }) {
const [url, setUrl] = useState(null);
useEffect(() => {
if (!data) return;
// Fireproof CBOR round-trips Uint8Array as plain objects with numeric keys
const bytes = data instanceof Uint8Array ? data : new Uint8Array(Object.values(data));
const objectUrl = URL.createObjectURL(new Blob([bytes], { type }));
setUrl(objectUrl);
return () => URL.revokeObjectURL(objectUrl);
}, [data, type]);
return url ? <img src={url} alt={alt} className={className} /> : null;
}
// Usage: <StoredImage data={doc.imageData} type={doc.imageType} alt="Photo" />merge()submit()merge()submit()database.put()reset()useDocumentdatabase.put()missing block// BAD — spreads internal metadata
await database.put({ ...doc, completed: true });
// GOOD — explicit fields only
await database.put({ _id: doc._id, type: doc.type, todo: doc.todo, completed: true });VibeContextProvideruseFireproofClerk()?external=react,react-dom/fireproof-vibes-bridge.js/fireproof-clerk-bundle.jsapp.jsx/vibes:vibes| Need | Signal in Prompt | Read This |
|---|---|---|
| File uploads | "upload", "images", "photos", "attachments" | |
| Auth / sync config | "Clerk", "Connect", "cloud sync", "login" | |
| Sync status display | "online/offline", "connection status" | |
| Full Neobrute design details | detailed design system, spacing, typography | |
/vibes:cloudflareQuestion: "Your app is live! Want to turn it into a product? The /sell skill adds multi-tenant SaaS with auth and billing. Or pick another direction:"
Header: "Next"
Options:
- Label: "Keep improving this app"
Description: "Continue iterating on what you've built. Add new features, refine the styling, or adjust functionality. Great when you have a clear vision and want to polish it further."
- Label: "Apply a design reference (/design-reference)"
Description: "Have a design.html or mockup file? This skill mechanically transforms your app to match it exactly - pixel-perfect fidelity with your Fireproof data binding preserved."
- Label: "Explore variations (/riff)"
Description: "Not sure if this is the best approach? Riff generates 3-10 completely different interpretations of your idea in parallel. You'll get ranked variations with business model analysis to help you pick the winner."
- Label: "Make it a SaaS (/sell)"
Description: "Ready to monetize? Sell transforms your app into a multi-tenant SaaS with Clerk authentication, subscription billing, and isolated databases per customer. Each user gets their own subdomain."
- Label: "Deploy to Cloudflare (/cloudflare)"
Description: "Go live on the edge. Deploy to Cloudflare Workers with a subdomain registry, KV storage, and global CDN. Fast, scalable, and always on."
- Label: "I'm done for now"
Description: "Wrap up this session. Your files are saved locally - come back anytime to continue."