Every mega-prompt in this archive collapses into the same shape: a single TanStack server function calling api.sprites.dev/v1 with one secret. It's the only pattern that lets a Lovable account ship a working Sprites demo in one shot, inside the 5-credit budget.
Sprites are fly.io micro-VMs behind one REST API: create a fresh sandbox in a second, drop files into it, run any command or long-running service, hand the user a public URL, let it sleep. No CI, no Dockerfile, no Kubernetes — every demo gets its own throwaway server on demand.
Lovable's TanStack Start template makes secrets trivial. createServerFn runs on the server, reads process.env.SPRITES_TOKEN, hits api.sprites.dev, and returns typed JSON. The token never reaches the browser, no edge functions or extra infra needed.
// src/lib/sprites.functions.ts — spin up a public Sprite
// Built during the Sprites Creative Hackathon — StreetKode Fam · Indian Krump Festival 14
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
const API = "https://api.sprites.dev/v1";
const auth = () => ({ Authorization: `Bearer ${process.env.SPRITES_TOKEN!}` });
export const launch = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({ name: z.string().min(1).max(48) }).parse(d))
.handler(async ({ data }) => {
const r = await fetch(`${API}/sprites`, {
method: "POST", // NOTE: POST-only. PUT returns 404.
headers: { ...auth(), "Content-Type": "application/json" },
body: JSON.stringify({ name: data.name, url_settings: { auth: "public" } }),
});
if (!r.ok && r.status !== 409) throw new Error(`create failed: ${r.status}`);
return { url: `https://${data.name}.sprites.run` };
});// src/lib/sprites.functions.ts — drop an index.html into a live Sprite
export const publish = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({ name: z.string(), html: z.string().min(1) }).parse(d))
.handler(async ({ data }) => {
// fs/write auto-creates parent dirs. Serve from /root/www — /home/sprite may not exist.
const w = await fetch(
`${API}/sprites/${data.name}/fs/write?path=/root/www/index.html&workingDir=/`,
{ method: "PUT", headers: { ...auth(), "Content-Type": "application/octet-stream" }, body: data.html },
);
if (!w.ok) throw new Error(`fs write failed: ${w.status}`);
return { ok: true };
});// src/lib/sprites.functions.ts — long-running service + warm-poll
export const serve = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({ name: z.string() }).parse(d))
.handler(async ({ data }) => {
// Reset stale, then PUT the new service. http_port is REQUIRED for wake-on-request.
await fetch(`${API}/sprites/${data.name}/services/webapp`, { method: "DELETE", headers: auth() });
await fetch(`${API}/sprites/${data.name}/services/webapp`, {
method: "PUT",
headers: { ...auth(), "Content-Type": "application/json" },
body: JSON.stringify({
cmd: "python3", args: ["-m", "http.server", "8080"],
dir: "/root/www", needs: [], http_port: 8080,
}),
});
await fetch(`${API}/sprites/${data.name}/services/webapp/start`, {
method: "POST", headers: { ...auth(), Accept: "application/x-ndjson" },
});
// Warm-poll so the first user hits a live URL, not a cold-boot 502.
const url = `https://${data.name}.sprites.run`;
for (let i = 0; i < 12; i++) {
try { if ((await fetch(url, { signal: AbortSignal.timeout(4000) })).ok) return { url }; } catch {}
await new Promise((r) => setTimeout(r, 1000));
}
return { url };
});// src/lib/sprites.functions.ts — one-shot exec inside a Sprite
export const run = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({ name: z.string(), script: z.string().min(1) }).parse(d))
.handler(async ({ data }) => {
const qs = new URLSearchParams();
qs.append("cmd", "bash"); qs.append("cmd", "-lc"); qs.append("cmd", data.script);
const res = await fetch(`${API}/sprites/${data.name}/exec?${qs}`, {
method: "POST",
headers: auth(), // Authorization ONLY. No Accept, or 406.
});
const bytes = new Uint8Array(await res.arrayBuffer());
// Response ends with 0x03 <exitCode>.
const exit = bytes.length >= 2 && bytes[bytes.length - 2] === 3 ? bytes[bytes.length - 1] : null;
const end = exit === null ? bytes.length : bytes.length - 2;
return { stdout: new TextDecoder().decode(bytes.slice(0, end)), exit };
});# .env (Lovable -> Project Settings -> Secrets)
SPRITES_TOKEN=<org-slug>/<org-id>/<token-id>/<token-value> # https://sprites.dev/account
# How Lovable wires this up in one prompt:
# 1. Paste a mega-prompt from this archive.
# 2. Lovable
# - writes a server function that proxies Sprites (create / fs / service / exec)
# - wires the client surface (launch button, compose form, console box, etc.)
# - keeps your token on the server via process.env.SPRITES_TOKEN
# 3. Run it. Your demo is spinning up real fly.io micro-VMs.SPRITES_TOKEN. That's the whole build./root/www with http_port: 8080. Warm-poll before returning the URL.Accept: application/octet-stream on /exec. Sprites returns 406.