Anatomy of a Vibe
The local files your AI writes and the CLI uploads
Last updated: September 2026
Starter template files
The CLI ships six templates. Start from tools, a blank data vibe on KV storage; tools-sql is the same on SQL (Pro or Pro+); tracker, log and journal are closer starting points on KV. Each of these creates the same small project shape:
my-vibe/
.vibe-coded.json # Local platform manifest
index.html # Frontend, styles, and browser code
worker.js # API routes, storage calls, and MCP tools
Run vibe templates to list them, then vibe init --template tools my-vibe to copy one. The sixth, site, is a static page: index.html only, no worker. Template metadata decides the storage type; its internal template.json is not copied into the project.
A static vibe can contain only index.html and the manifest. When you ask your coding agent for a Vue and TypeScript app, the skill uses this structure:
my-vibe/
.vibe-coded.json # Local platform manifest
package.json # Dependencies and build script
tsconfig.json # TypeScript configuration
vite.config.ts # Vite and Vue configuration
index.html # HTML shell
worker.ts # Backend, for a full-stack vibe
src/
main.ts # Vue app initialization
vite-env.d.ts # Vite type declarations
App.vue # Root component
Your AI can add components, composables, views, and a hash-based Vue router as the app grows.
What vibe preview uploads
vibe preview uploads the local source tree and builds it on the platform. It skips node_modules, dist, .git, .vibe-coded, .vibe-coded.json, source maps, .DS_Store, .env, files named .env.*, .dev.vars, .sql files, and agent or editor folders such as .claude, .cursor and .vscode. The platform also refuses file types outside its allowlist and any name containing password, credential or .secret; see File type not accepted in the CLI guide. Do not keep credentials in the source tree.
By default, preview also removes remote source files that no longer exist locally, in the same step as the upload. Use --no-prune to keep remote-only files, or --prune to confirm a large removal.
What a vibe without a build step serves. If your vibe has no dist/ folder, its files are served as they are, with some exceptions: the root worker (worker.js or worker.ts), config files (wrangler.*, vite.config.*, tsconfig*, package.json and lockfiles, vibe.json, template.json, .vibe-coded.json), and notes such as README.md are never served, even to you as the owner. Pages, scripts, styles and images are, and so are local modules the worker imports, such as lib/helper.js, and any data files in the folder. So keep server-only code in the root worker file, or build to dist/. Check what's live with vibe fetch <path>.
The frontend
The frontend can be plain HTML and JavaScript or a Vite app. Calls to the worker use relative, snake-case /api/ paths and POST:
const response = await fetch('/api/list_notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tag: 'idea' })
})
const { success, data, error } = await response.json()
The worker and MCP tools
worker.ts or worker.js is a self-contained Cloudflare Worker. It handles /api/ requests, uses platform storage bindings, and returns { success, data } or { success: false, error }.
This is add_note from the tools template:
/**
* @mcp-expose
* @description Save a note. Returns the saved note with its id.
* @param {string} text - The note (required)
* @param {string} [tag] - Optional tag for grouping, e.g. "idea"
*/
async function add_note(text, tag, env) {
if (!text || typeof text !== 'string') return { success: false, error: 'text is required' };
if (tag !== undefined && typeof tag !== 'string') return { success: false, error: 'tag must be a string' };
const id = crypto.randomUUID();
const now = new Date().toISOString();
const note = { id, text, tag: tag || null, createdAt: now, updatedAt: now };
await env.VIBE_STORAGE.put(`notes:${id}`, JSON.stringify(note));
await rememberWrite(env, 'notes:', id);
return { success: true, data: note };
}
KV's list() can miss a new record for up to a minute. rememberWrite keeps a short list of recent ids, and the template's loadAll helper, which list_notes uses, merges it with list(), so a note your AI just added normally shows up the next time it asks for the list.
The build extracts a tool only when @mcp-expose is attached to an async function declaration. Unannotated helpers, arrow functions, and object methods are not exposed. @mcp-scope is optional metadata; it does not grant access. Access is set per vibe by role, with viewers in read mode and writers, admins, and owners in write mode.
Storage bindings
KV is available on every plan. SQL and file storage require Pro ($9/month) or Pro+ ($20/month). See Plans & Billing for quotas instead of copying them into your app documentation.
| Binding | Storage | Plans | Typical operations |
|---|---|---|---|
env.VIBE_STORAGE |
KV | Free, Pro, Pro+ | get, put, delete, list |
env.VIBE_D1 |
SQL | Pro, Pro+ | prepare, bind, run, all |
env.VIBE_R2 |
Files | Pro, Pro+ | put, get, delete, list |
Changing a SQL schema
Preview and production each have their own database. The preview's starts empty, so have the worker create its own tables with CREATE TABLE IF NOT EXISTS the first time a write tool runs, as the tools-sql template does, and apply schema changes the same way. When you publish, production runs the same code against its own data. Publishing copies no data, so the migration runs again there. Keep migrations additive (add columns or tables, don't rename or drop them), so production's existing rows keep working.
Local, preview, and production
Use vibe dev while editing. It runs the worker with local KV, D1, and R2 data and reloads it when you save worker code. Other files are served from your project as they are — it doesn't run Vite — so refresh the browser after frontend edits and use vibe preview to see a built Vue app. Use vibe preview when you need to test a live build, platform access checks, OAuth, or the full MCP transport. Previewing never changes production. After checking the preview URL, vibe publish deploys that exact build to production. Preview and production keep separate data (key-value storage, SQL and files): test records you add on the preview stay there, and publishing copies none of them. Calls your code makes to the outside world, such as webhooks or email, are real on the preview too.