One Codebase. 13 Platforms. Zero Compromises.
How three files become a web form, CLI command, MCP tool, native screen, and automated job - simultaneously.
5,802 TypeScript files. ~2.1 million lines. Zero `any`. Zero runtime type errors. One pattern. Repeated 374 times.
That's the codebase behind unbottled.ai - and the framework that powers it, next-vibe. The same architecture runs a web app, a mobile app, a CLI, an AI agent interface, an MCP server, a cron system, a websocket event bus, and a live dataflow engine.
The pattern is called the Unified Surface. Here's what it is, how it works, and why - once you see it - you'll find it hard to go back.
A feature is a folder
Every feature in next-vibe lives in a folder. Three files are required. Everything else is optional.
1explain-to-my-boss/
2 definition.ts ← what it does
3 repository.ts ← how it does it
4 route.ts ← makes it exist everywhere
5 widget.tsx ← custom React UI (optional)
6 widget.cli.tsx ← custom terminal UI (optional)That's it. One folder. Three required files. And from those three files, that feature exists on up to 13 platforms simultaneously.
13 platforms from 3 files
When you add a feature to next-vibe, it doesn't just become an API endpoint. It becomes everything at once.
Web API
REST endpoint, auto-validated, fully typed
React UI
Auto-generated from the definition - no JSX written
CLI
Every endpoint is a command with auto-generated flags
AI Tool Schema
Function calling schema generated automatically
MCP Server
Plug into Claude Desktop, Cursor, or any MCP client
React Native
iOS and Android screens from the same definition
Cron Job
Schedule any endpoint to run on a timer
WebSocket Events
Push updates to connected clients on completion
Electron Desktop
Native desktop app via the same endpoint contracts
Admin Panel
Auto-generated admin UI, no bespoke code needed
VibeFrame Widget
Embeddable iframe widget for any website
Agent Skill
Callable by AI agents as a structured skill
Vibe Sense Node
Node in a live dataflow graph - same endpoint
Delete the folder. The feature ceases to exist everywhere at once.
Platform access is one enum array
You don't write separate permission layers for each platform. Platform access is declared in the definition itself - one enum array that every platform reads natively at runtime.
1// This single array controls where the feature appears
2allowedRoles: [
3 CLI_OFF, // blocks the CLI
4 MCP_VISIBLE, // opts into the MCP tool list
5 REMOTE_SKILL, // puts it in the agent skill file
6 PRODUCTION_OFF, // disables it in prod
7]Same definition. Same place. No config files to sync. No separate permission systems per platform.
There is no API for humans and API for AI. There's just the tool.
The live demo: Thea builds an endpoint
Instead of explaining the pattern abstractly, let me show you what it looks like in practice.
Thea is the AI admin for this platform. She runs on production 24/7, operating through the exact same endpoint contracts as every user - same validation, same permissions, no backdoor. And she can delegate work to a local machine.
I asked Thea to build a new endpoint - explain-to-my-boss - using Claude Code running on my PC. You give it a technical decision. It gives you a non-technical justification your manager will actually believe. Every developer has needed this.
The live demo: Thea builds an endpoint
Ask Thea
Type the task in the chat - two input fields, one AI-generated response, all platforms, MCP_VISIBLE, custom React and CLI widgets.
Creates task
Thea reasons out loud, creates a task with targetInstance="hermes" (your local machine), and goes dormant.
Picks up the task
The local instance syncs every 60 seconds. No open ports. Your machine initiates the connection.
Builds the endpoint
Interactive session. Reads existing patterns first, creates five files, runs vibe check. Zero errors.
Reports completion
Calls complete-task with the task ID. Status: completed. Summary attached.
Wakes up
wakeUp fires. Thea resumes mid-conversation via websocket, streams her response, TTS speaks.
Exists everywhere
Web form. CLI command. MCP tool. React Native screen. All live. From five files.
The proof
Once Claude Code called complete-task, three things existed that didn't five minutes before:
A custom React widget - dramatic heading, animated gradient on the AI output, a fake corporate alignment score.
A CLI widget - ASCII banner, spinner while the AI thinks, the justification printed line by line in green.
An MCP tool - explain-to-my-boss_POST - because MCP_VISIBLE was in the definition. Claude Desktop can now explain your decisions to your boss on your behalf.
One definition. Five files total. Three completely different UIs. The endpoint contract didn't change. Only the presentation layer did.
Under the hood
definition.ts - the living contract
The definition is not a code generator. It's a living contract that every platform reads natively at runtime. Change it - everything updates. Delete the folder - nothing breaks downstream. There's no generated code to clean up.
1// definition.ts
2const { POST } = createEndpoint({
3 scopedTranslation,
4 aliases: [EXPLAIN_TO_MY_BOSS_ALIAS],
5 method: Methods.POST,
6 path: ["explain", "to-my-boss"],
7 title: "post.title",
8 description: "post.description",
9 icon: "sparkles",
10 category: "endpointCategories.ai",
11 allowedRoles: [UserRole.CUSTOMER, UserRole.ADMIN] as const,
12 fields: objectField(scopedTranslation, {
13 type: WidgetType.CONTAINER,
14 usage: { request: "data", response: true },
15 children: {
16 decision: requestField(scopedTranslation, {
17 type: WidgetType.FORM_FIELD,
18 fieldType: FieldDataType.TEXTAREA,
19 label: "post.fields.decision.label",
20 description: "post.fields.decision.description",
21 schema: z.string().min(1).max(2000),
22 columns: 12,
23 }),
24 justification: responseField(scopedTranslation, {
25 type: WidgetType.TEXT,
26 content: "post.fields.justification.content",
27 schema: z.string(),
28 }),
29 },
30 }),
31 errorTypes: { /* ... all 9 required error types ... */ },
32 successTypes: { title: "post.success.title", description: "post.success.description" },
33 examples: { requests: { default: { decision: "Migrate to Bun" } }, responses: { default: { justification: "..." } } },
34});repository.ts - no throw, ever
Repository functions never throw. Errors propagate as data - typed, explicit, and catchable by the caller. The AI can reason about failure paths. No surprise exceptions.
1// repository.ts
2// Returns ResponseType<T> - never throws
3export async function explainToMyBoss(
4 data: { decision: string },
5 logger: Logger,
6): Promise<ResponseType<{ justification: string }>> {
7 const result = await ai.generateText({
8 prompt: buildPrompt(data.decision),
9 });
10 if (!result.text) {
11 return fail({ message: "AI returned empty response", errorType: EndpointErrorTypes.SERVER_ERROR });
12 }
13 return success({ justification: result.text });
14}route.ts - the entire bridge
route.ts wires the definition to the handler. endpointsHandler takes care of validation, auth, logging, and exposure to all 13 platforms. The actual business logic is one line.
1// route.ts
2import { endpointsHandler } from "@/app/api/[locale]/system/unified-interface/shared/endpoints/route/multi";
3import { Methods } from "@/app/api/[locale]/system/unified-interface/shared/types/enums";
4import definitions from "./definition";
5import { explainToMyBoss } from "./repository";
6
7export const { POST, tools } = endpointsHandler({
8 endpoint: definitions,
9 [Methods.POST]: { handler: ({ data, logger }) => explainToMyBoss(data, logger) },
10});The numbers
374 endpoints
One pattern, applied 374 times
Zero `any`
Enforced at build time, not convention
Three languages
Compile-time checked - t("typo.here") is a compiler error
That's not convention. It's enforced at build time.
One pattern. Repeated 374 times.
Every node in a Vibe Sense graph is a regular next-vibe endpoint. The same createEndpoint(). The same 3-file structure. An EMA indicator is an endpoint. A threshold evaluator is an endpoint. And because it's an endpoint - you can call it from the CLI, from the AI, from anywhere.
vibe analytics/indicators/ema --source=leads_created --period=7
The pipeline is the platform.
Vibe Sense is just... more endpoints. The same thing, applied to time series data. Lead funnels. Credit economy. User growth. Your platform watching itself.
Back to BlogDefine it once. It exists everywhere.
WordPress gave everyone the power to publish. next-vibe gives you the power to build platforms that work on web, mobile, CLI, AI agents, and automation - that watch themselves, reason about their own data, and act on what they find.
Star next-vibe on GitHubChat, create, and connect - text, images, video, and music
Privacy-first AI with 119 models - chat, images, video & music
© 2026 unbottled.ai. All rights reserved.