Powrót do bloga
Architektura
12 min czytania

Jedna baza kodu. 13 platform. Zero kompromisów.

Jak trzy pliki stają się formularzem webowym, poleceniem CLI, narzędziem MCP, ekranem natywnym i automatycznym zadaniem - jednocześnie.

definition.ts → web · cli · mcp · native · cron · 10 więcej

5 802 pliki TypeScript. ~2,1 miliona linii. Zero `any`. Zero błędów typów w czasie wykonania. Jeden wzorzec. Powtórzony 374 razy.

To jest baza kodu stojąca za unbottled.ai - i framework, który ją napędza, next-vibe. Ta sama architektura obsługuje aplikację webową, mobilną, CLI, interfejs agenta AI, serwer MCP, system cron, magistralę zdarzeń WebSocket i silnik przepływu danych na żywo.

Wzorzec nazywa się Unified Surface. Oto czym jest, jak działa i dlaczego - gdy już go zobaczysz - trudno jest wrócić.

Feature to folder

Każda funkcja w next-vibe żyje w folderze. Trzy pliki są wymagane. Wszystko inne jest opcjonalne.

bash
1explain-to-my-boss/
2  definition.ts    ← co robi
3  repository.ts    ← jak to robi
4  route.ts         ← sprawia, że istnieje wszędzie
5  widget.tsx       ← własny interfejs React (opcjonalnie)
6  widget.cli.tsx   ← własny interfejs terminala (opcjonalnie)

To wszystko. Jeden folder. Trzy wymagane pliki. A z tych trzech plików funkcja istnieje na maksymalnie 13 platformach jednocześnie.

13 platform z 3 plików

Gdy dodajesz funkcję do next-vibe, nie staje się ona tylko punktem końcowym API. Staje się wszystkim naraz.

Web API

Endpoint REST, automatycznie walidowany, w pełni typowany

React UI

Auto-generowany z definicji - bez pisania JSX

CLI

Każdy endpoint to polecenie z auto-generowanymi flagami

Schemat narzędzia AI

Schemat function calling generowany automatycznie

Serwer MCP

Połącz z Claude Desktop, Cursor lub dowolnym klientem MCP

React Native

Ekrany iOS i Android z tej samej definicji

Zadanie Cron

Zaplanuj dowolny endpoint do uruchamiania według harmonogramu

Zdarzenia WebSocket

Wypychaj aktualizacje do podłączonych klientów po zakończeniu

Aplikacja Electron

Natywna aplikacja desktopowa przez te same kontrakty endpoint

Panel administracyjny

Auto-generowany interfejs admina, bez dedykowanego kodu

Widget VibeFrame

Osadzalny widget iframe dla dowolnej strony

Umiejętność agenta

Wywoływalna przez agentów AI jako strukturalna umiejętność

Węzeł Vibe Sense

Węzeł w grafie przepływu danych na żywo - ten sam endpoint

Usuń folder. Feature przestaje istnieć wszędzie naraz.

Dostęp do platformy to jedna tablica enum

Nie piszesz oddzielnych warstw uprawnień dla każdej platformy. Dostęp do platformy jest deklarowany w samej definicji - jedna tablica enum, którą każda platforma odczytuje natywnie w czasie wykonania.

typescript
1// Ta pojedyncza tablica kontroluje, gdzie pojawia się feature
2allowedRoles: [
3  CLI_OFF,         // blokuje CLI
4  MCP_VISIBLE,     // opt-in do listy narzędzi MCP
5  REMOTE_SKILL,    // umieszcza w pliku umiejętności agenta
6  PRODUCTION_OFF,  // wyłącza w produkcji
7]

Ta sama definicja. To samo miejsce. Brak plików konfiguracyjnych do synchronizacji. Brak oddzielnych systemów uprawnień na platformę.

Nie ma API dla ludzi i API dla AI. Jest tylko narzędzie.

Demo na żywo: Thea buduje endpoint

Zamiast abstrakcyjnie wyjaśniać wzorzec, pozwól mi pokazać, jak wygląda w praktyce.

Kim jest Thea?

Thea to administrator AI tej platformy. Działa na produkcji 24/7, operując przez te same kontrakty endpoint co każdy użytkownik - ta sama walidacja, te same uprawnienia, bez tylnych drzwi. I może delegować pracę do lokalnej maszyny.

Poprosiłem Thea o zbudowanie nowego endpointu - explain-to-my-boss - używając Claude Code na moim PC. Podajesz techniczną decyzję. Zwraca nietech niczne uzasadnienie, w które twój menedżer naprawdę uwierzy. Każdy programista tego potrzebował.

Demo na żywo: Thea buduje endpoint

1
Ty

Zapytaj Thea

Wpisz zadanie w czacie - dwa pola wejściowe, jedna odpowiedź generowana przez AI, wszystkie platformy, MCP_VISIBLE, własne widgety React i CLI.

2
Thea

Tworzy zadanie

Thea myśli głośno, tworzy zadanie z targetInstance="hermes" (twoja lokalna maszyna) i przechodzi w stan uśpienia.

3
Lokalny Hermes

Odbiera zadanie

Lokalna instancja synchronizuje się co 60 sekund. Brak otwartych portów. Twoja maszyna inicjuje połączenie.

4
Claude Code

Buduje endpoint

Sesja interaktywna. Najpierw czyta istniejące wzorce, tworzy pięć plików, uruchamia vibe check. Zero błędów.

5
Claude Code

Raportuje ukończenie

Wywołuje complete-task z ID zadania. Status: ukończono. Załączone podsumowanie.

6
Thea

Budzi się

wakeUp uruchamia się. Thea wznawia rozmowę przez WebSocket, strumieniuje odpowiedź, TTS mówi.

7
Wynik

Istnieje wszędzie

Formularz web. Polecenie CLI. Narzędzie MCP. Ekran React Native. Wszystko na żywo. Z pięciu plików.

Dowód

Gdy Claude Code wywołał complete-task, trzy rzeczy istniały, których pięć minut wcześniej nie było:

Własny widget React - dramatyczny nagłówek, animowany gradient na wyjściu AI, fałszywy wynik dopasowania korporacyjnego.

Widget CLI - baner ASCII, spinner gdy AI myśli, uzasadnienie drukowane linia po linii na zielono.

Narzędzie MCP - explain-to-my-boss_POST - ponieważ MCP_VISIBLE było w definicji. Claude Desktop może teraz wyjaśniać twoje decyzje twojemu szefowi.

Jedna definicja. Łącznie pięć plików. Trzy zupełnie różne interfejsy. Kontrakt endpoint się nie zmienił. Tylko warstwa prezentacji.

Pod maską

definition.ts - żyjący kontrakt

Definicja nie jest generatorem kodu. To żyjący kontrakt, który każda platforma odczytuje natywnie w czasie wykonania. Zmień ją - wszystko się aktualizuje. Usuń folder - nic nie psuje się w dół strumienia. Nie ma wygenerowanego kodu do posprzątania.

typescript
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 - nigdy throw

Funkcje repozytorium nigdy nie rzucają. Błędy propagują jako dane - typowane, explicite i możliwe do złapania przez wywołującego. AI może rozumować o ścieżkach błędów. Brak niespodziewanych wyjątków.

typescript
1// repository.ts
2// Zwraca ResponseType<T> - nigdy nie rzuca
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 - cały most

route.ts łączy definicję z handlerem. endpointsHandler obsługuje walidację, uwierzytelnianie, logowanie i ekspozycję dla wszystkich 13 platform. Właściwa logika biznesowa to jedna linia.

typescript
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});

Liczby

374 endpointy

Jeden wzorzec, zastosowany 374 razy

Zero `any`

Wymuszane w czasie budowania, nie konwencja

Trzy języki

Sprawdzane w czasie kompilacji - t("typo.here") to błąd kompilatora

To nie jest konwencja. Jest wymuszane w czasie budowania.

Jeden wzorzec. Powtórzony 374 razy.
Co dalej
Vibe Sense: Potok jest platformą

Każdy węzeł w grafie Vibe Sense to zwykły endpoint next-vibe. Ten sam createEndpoint(). Ta sama struktura 3 plików. Wskaźnik EMA to endpoint. Ewaluator progowy to endpoint. I ponieważ to endpoint - możesz go wywołać z CLI, z AI, skądkolwiek.

vibe analytics/indicators/ema --source=leads_created --period=7

Potok jest platformą.

Vibe Sense to po prostu... więcej endpointów. To samo, zastosowane do danych szeregów czasowych. Lejki leadów. Ekonomia kredytów. Wzrost użytkowników. Twoja platforma obserwuje siebie.

Powrót do bloga

Zdefiniuj raz. Istnieje wszędzie.

WordPress dał każdemu moc publikowania. next-vibe daje ci moc budowania platform, które działają w sieci, na urządzeniach mobilnych, w CLI, agentach AI i automatyzacji - które obserwują siebie, rozumują o własnych danych i działają na podstawie tego, co znajdą.

Daj gwiazdkę next-vibe na GitHub