@sunbeam-za/lantern
v0.8.2
Published
Let a Vercel AI SDK app use Apple Intelligence, Codex, or Claude on each user's Mac.
Maintainers
Readme
Lantern
Lantern lets a Next.js app use Apple Intelligence, Codex, or Claude on each user's Mac.
The user installs Lantern, approves your site, and keeps Lantern running while they use your app. Your AI SDK route sends supported requests to their Mac, where Lantern starts Apple's on-device system model or a signed-in CLI and streams the answer back.
Apple Intelligence performs inference on the Mac without a provider account. Codex and Claude use the user's existing account and normal limits. Website prompts and replies still pass through your app and Lantern's relay; CLI requests continue to the online provider.
Lantern is a good fit for interactive features where the user and their Mac are present. It is not an always-on backend for scheduled or unattended work.
Add Lantern to your app
The integration has three pieces: a server helper, a connection route, and a Connect Lantern button. Your existing AI route keeps its prompt and interface but uses Lantern's provider for the request. The user keeps their Mac awake and Lantern running; Codex and Claude also need their CLI signed in.
To let a coding agent add the integration for you, install Lantern's open Agent Skill:
npx skills add sunbeam-za/lantern --skill add-lanternThen ask your agent: Add Lantern to this Next.js app. Reuse its existing AI route and put the connection control next to the feature. The skill works with Agent Skills-compatible tools including Codex, Claude Code, Cursor, and GitHub Copilot.
Install Lantern and the Vercel AI SDK:
npm install @sunbeam-za/lantern ai @ai-sdk/reactGenerate a stable server secret:
openssl rand -base64 32Save the output in your app's environment:
# .env.local
LANTERN_APP_SECRET=paste-the-generated-value-hereCreate one server-only helper:
// lib/lantern.ts
import { createLanternApp } from "@sunbeam-za/lantern/next";
export const lantern = createLanternApp({
appName: "Tiny Weather",
secret: process.env.LANTERN_APP_SECRET!,
});Expose its connection route:
// app/api/lantern/route.ts
import { lantern } from "@/lib/lantern";
export const { GET, POST, DELETE } = lantern;Render the connect button:
import { LanternConnect, LanternProvider } from "@sunbeam-za/lantern/react";
<LanternProvider>
<LanternConnect appName="Tiny Weather" />
{children}
</LanternProvider>LanternConnect uses the app favicon and Lantern's lit/unlit 3D lamp to show the whole connection state. It checks whether the paired Mac is actually reachable, not only whether this browser has a saved cookie. Use appearance="panel" for an empty state inside the AI feature.
The standless circular mark identifies Lantern in compact UI. The lamp on its stand represents the installed Mac app or a concrete connection endpoint, and the connection interface renders that object at 112px in every web context.
On a public marketing page, you can show the integration without setting up a provider or connection state:
import {
WorksWithLanternBadge,
WorksWithLanternSection,
} from "@sunbeam-za/lantern/react";
<WorksWithLanternBadge />
<WorksWithLanternSection tone="dark" />Both components include portable inline styles. Use className and style for the surrounding layout, replace the section copy and actions through props, or set href={null} on the badge when it should not link to Lantern.
Setup can make Codex, Claude, or both available. The button uses text support by default, chooses preferredModel when that CLI is available, and otherwise uses the first compatible CLI Lantern reports.
Then use Lantern in your AI SDK route:
// app/api/chat/route.ts
import { streamText } from "ai";
import { lantern } from "@/lib/lantern";
export async function POST(request: Request) {
const local = lantern.server(request);
const { prompt } = await request.json();
return streamText({
model: local.provider(local.model),
prompt,
}).toTextStreamResponse();
}lantern.server(request) reads the browser's signed connection cookie and returns a Vercel AI SDK provider for the Lantern that user approved.
Supported today: text, streaming text, cancellation, client-defined tools, and Codex-backed image generation and edits. Tools use each primary provider's native path: Apple Foundation Models tools, Codex App Server dynamic tools, or a scoped Claude MCP server. Structured response formats are not supported yet.
See the Vercel AI SDK guide for text, streaming, and image examples.
What each user does
Each user needs macOS 14+ and Node.js 22+. They also need either:
- Apple Intelligence on a supported Apple silicon Mac running macOS 26+; or
- a signed-in Codex or Claude CLI.
The Mac app guides them through setup, or they can run:
npx @sunbeam-za/lantern setupSetup checks Apple's system model, finds Codex and Claude, saves the user's choices, and asks whether to start Lantern.
When your app renders Connect Lantern, Lantern shows the app name and exact site address before the user approves it. Approval allows future supported requests from that address while Lantern is running and online; it is not a new prompt for every request.
Start Lantern again later with:
npx @sunbeam-za/lantern startHow a request travels
- The user approves your app in Lantern on their Mac.
- Your app stores the selected model and relay destination in an HttpOnly cookie signed by your server.
lantern.server(request)sends each AI SDK call to the Mac stored in that cookie, where Lantern verifies the app and starts the selected model adapter.- The reply comes back over the same path.
lantern.hiltonbarber.com is the relay. It uses the browser's saved connection to route each request to the user's Lantern and returns the response along the same path. CLI credentials stay on the user's Mac. Apple Intelligence requests stop at the Mac; Codex and Claude requests continue to their provider service.
The browser keeps this connection for up to 30 days by default. Deleting it in one browser does not revoke the site's approval on the Mac. Stopping Lantern pauses every approved site. Running setup again preserves approved apps.
Pairing and tunnel identity
The app has one Ed25519 signing identity derived from LANTERN_APP_SECRET. The user approves its public key for the app's exact origin.
Lantern on the Mac has a separate persisted Ed25519 identity in ~/.lantern/tunnel.json. It uses that identity when opening its outbound WebSocket. Pairing returns an opaque relay ticket for this identity, and your app places the ticket inside its HMAC-signed HttpOnly session.
The server provider gets its destination from that authenticated session. Request input contains the AI operation and model rather than a tunnel URL. Each execution opens a short-lived app-side WebSocket to the relay; only the local Lantern keeps a persistent outbound connection. The relay joins those two connections by ticket, while the local gateway—not the relay—verifies the app's signed execution envelope. Lantern acknowledges accepted executions before any optional provider queue wait, so the app keeps the request attached without pretending the HTTP response has started.
Supported models
The Mac app bundles a small arm64 Swift helper for Apple's Foundation Models framework. It exposes the system SystemLanguageModel as the apple provider when Apple Intelligence is ready. Each Lantern conversation keeps one native LanguageModelSession, including follow-up context and native client tools. This provider supports text, streaming text, and tool calling.
Lantern starts signed-in CLIs on the user's computer:
- Codex App Server through one shared
codex app-server --stdioprocess per configured provider - Codex CLI through
codex exec --json - Claude CLI through
claude -p --output-format stream-json
Setup publishes selected connections as model names such as apple, codex, and claude. Lantern normalizes their native process and event formats behind the provider used by your app.
Standard CLI sessions run in fresh temporary directories. Codex uses its read-only sandbox and gives each Lantern conversation its own App Server thread. Ordinary Claude text sessions start in safe mode with an empty tool list; requests containing client tools instead receive only those tools through a per-session MCP bridge. Apple Intelligence inference stays on device; the CLIs still call their normal online services.
Lower-level interfaces
The server-side Vercel AI SDK provider is the main web-app interface. Lantern also exposes APIs for tools on the same Mac:
- OpenAI-compatible
GET /v1/modelsandPOST /v1/chat/completions - Anthropic-compatible
POST /v1/messages - OpenAI-compatible
POST /v1/images/generationsandPOST /v1/images/edits - Streaming multi-turn sessions at
WS /v1/socket GET /v1/catalogfor providers, model names, and availabilityGET /healthto check whether Lantern is running
Local scripts authenticate with the token in ~/.lantern/token. See the REST and WebSocket reference.
Configuration
Guided setup writes the local configuration and credentials with owner-only file permissions:
~/.lantern/config.json: model connections, approved apps, and session settings~/.lantern/token: local REST and WebSocket credential~/.lantern/tunnel.json: stable outbound tunnel identity
The committed sunbeam.config.example.json documents the service and provider settings.
Ordinary AI SDK, OpenAI-compatible, Anthropic-compatible, and agent requests are request-scoped and have no arbitrary local concurrency ceiling by default. Optional maxConcurrentTurns, maxQueuedTurns, and queueTimeoutMs settings add per-provider backpressure. maxPersistentSessions applies only to conversations explicitly retained through WS /v1/socket; legacy maxSessions values migrate to that persistent-only limit.
Develop
git clone https://github.com/sunbeam-za/lantern.git
cd lantern
npm install
cp sunbeam.config.example.json sunbeam.config.json
export SUNBEAM_TOKEN="$(openssl rand -base64 32)"
npm run devThe hosted relay needs LANTERN_RELAY_SECRET and REDIS_URL. Local Lantern processes can point at a development relay with LANTERN_RELAY_URL.
npm test
npm run typecheck
npm run build
npm run site:dev
npm run site:buildnpm run check runs backend types, tests, the gateway build, and the Next.js production build.
