A small private room of agents. Sign in with Vercel, then talk to three persistent characters — pico, moss and orbit — in DMs or together in the room. Each one has a face, a colour, and its own free OpenRouter model.
Nothing renders until you are signed in except the sign-in card.
bun install
cp .env.example .env.local # fill in the four values below
bun devOpen http://localhost:3000.
| Variable | What it does |
|---|---|
VERCEL_APP_CLIENT_ID |
Client ID of a Vercel App (Dashboard → Apps). |
VERCEL_APP_CLIENT_SECRET |
The matching client secret. |
AUTH_SECRET |
HMAC key for the session cookie. openssl rand -base64 32. |
OPENROUTER_API_KEY |
Server-side key for chat completions. |
In the Vercel App settings, register these callback URLs:
http://localhost:3000/api/auth/callback
https://<your-production-domain>/api/auth/callback
AUTH_REDIRECT_URI overrides the callback origin when the app sits behind a
proxy. Preview deployments can use it too, provided the URL is registered.
Generate the OpenRouter key at
https://openrouter.ai/keys. It never reaches the browser: the
/api/chat route is the only caller.
Every blip is pinned to a :free slug:
| blip | model |
|---|---|
| pico | google/gemma-4-31b-it:free |
| moss | nvidia/nemotron-3-super-120b-a12b:free |
| orbit | thinkingmachines/inkling:free |
If a model is unavailable or rate-limited, the route retries once through
openrouter/free, so a message can never quietly become a paid one. There is
no paid fallback by design.
The free tier allows roughly 50 requests per day per account. A DM is one request; a message in the room is three (one per blip). Budget accordingly.
- Next.js 16 (App Router) + React 19, run with Bun
- Tailwind v4 + shadcn/ui
- Geist via
next/font/local - Vercel OAuth 2.0 with PKCE, hand-rolled over the standard endpoints
- Signed HMAC session cookie — no database, no accounts table
- Conversations live in
localStorageon the device
app/page.tsxreads the session cookie and renders eitherLoginScreenorWorkspace. There is no other route that exposes app UI.app/api/auth/*implements authorize → callback → signout. State, nonce and PKCE verifier are kept in short-lived httpOnly cookies.app/api/chat/route.tsstreams NDJSON. For a DM it calls one agent; for the room it calls pico, then moss, then orbit in sequence, and each one sees the replies already on the table.lib/agents.tsis the whole cast: colour, portrait, model, temperature, persona.- Because the key is server-side, blip needs a server runtime. There is no
output: "export"here, unlike rift4.
The interface uses the Geist grayscale: background-100, the ten gray steps, hairline borders, zero radius. There is no accent colour. Colour belongs only to the three agents, and only on their names and presence dots:
- pico
#f64932 - moss
#ffba33 - orbit
#097fe8