~2 min read
Get started·
tiny-vercel is two deployables and one shared protocol package, all in one repository:
| Path | What it is | Where it runs |
|---|---|---|
apps/web |
Next.js 16 App Router — every page, all 65 app/api/* routes, the agent tools |
Vercel |
apps/worker |
Cloudflare Worker — D1 (SQL), KV, Vectorize, R2, a Durable Object for voice, a 1-minute cron | Cloudflare |
packages/contracts |
TypeScript wire protocol: SSE events, device relay envelopes, notification payloads, x402 quotes | both |
examples/echo-device |
the smallest endpoint device (telemetry / chat / snapshot behind a bearer) + an enroll script | your laptop |
scripts/ |
bootstrap-cloudflare.mjs creates the worker's storage under one prefix; teardown-cloudflare.mjs removes exactly that |
your laptop |
Pick the path that matches what you want right now.
Deploy in 15 minutesCloudflare bootstrap → wrangler deploy → Vercel button → first login → passkey. The exact sequence that produced the live demo.
Run locallyMiniflare for the worker, Next.js dev server for the app. No Cloudflare or Vercel account needed.
Your first tinySign in, create an AI by chatting, give it knowledge and tools, share its profile card.
Connect a devicePoint npx tiny-vercel at your origin, or enroll the 90-line echo endpoint device.
CLI: npx tiny-vercelThe production fleet's CLI, in the repo: MCP server for Claude Code, local agent, device daemon — one init points it at you.
What you need·
- A Cloudflare account — the free plan covers D1, KV, Vectorize, R2 and Workers.
- A Vercel account and a GitHub account (GitHub is the first-login identity provider; passkeys come after).
- An OpenAI API key. It is the default chat model and the embedding model (
text-embedding-3-small). Chat can move to Bedrock, Gemini, Vercel AI Gateway or a user's own key later; embeddings stay on OpenAI. - Node 22 and
npmon your machine.
What runs where·
flowchart LR
B[Browser / PWA] -->|HTTPS + SSE| W[apps/web on Vercel]
D1[Dial-in device<br/>tiny-vercel CLI, phone, e-ink] -->|heartbeat · relay mailbox| W
W -->|X-Internal-Key| K[apps/worker on Cloudflare]
K --> S[(D1 · KV · Vectorize · R2 · DO)]
K -->|bearer, https only| D2[Dial-out device<br/>robot arm, printer, echo]
The app never talks to storage directly: every internal call goes to the worker with INTERNAL_API_KEY, and the worker refuses anything else. Set the same value on both sides or every call is a 401.