Easel Posture Prompter
Easel Posture Prompter runs a one-shot shell command inside a Sprite for studio ergonomics and streams the stdout back so the user watches the work happen live.
The primitive.
Painters run a shell command inside a Sprite for studio ergonomics and watch the stdout stream back — ffmpeg, imagemagick, a python one-liner — in an isolated micro-VM they never had to install.
Why this primitiveOne-shot Exec fits studio ergonomics in Visual Art because the user really wants to run a command and see stdout — ffmpeg, imagemagick, a python one-liner — inside an isolated micro-VM they don't have to set up.
Required key.
Add this in your Lovable project under Settings → Secrets before pasting the prompt below.
The build prompt.
Paste into a fresh Lovable project. Make sure the key above is set first. read the build strategy →
Build "Easel Posture Prompter" as a ONE-SHOT Lovable build. The participant has only
5 credits — this single message must produce a working demo with no follow-ups.
Single-page TanStack Start app. Cut scope ruthlessly.
CONCEPT
Easel Posture Prompter runs a one-shot shell command inside a Sprite for studio ergonomics and streams the stdout back so the user watches the work happen live.
Discipline: Visual Art (studio ergonomics).
Recipe: One-shot Exec (run any shell command) as the single Sprites primitive.
Why Sprites: One-shot Exec fits studio ergonomics in Visual Art because the user really wants to run a command and see stdout — ffmpeg, imagemagick, a python one-liner — inside an isolated micro-VM they don't have to set up.
SPRITES PRIMER (assume the reader has never used Sprites)
- What they are: fly.io micro-sandboxes (tiny disposable Linux VMs) managed via
a REST API at `https://api.sprites.dev/v1`. Each sprite gets a public URL at
`https://{name}.sprites.run` when created with `url_settings.auth: "public"`.
- Sign-up: create a free account at https://sprites.dev, then copy the 4-part
token (`org-slug/org-id/token-id/token-value`) from https://sprites.dev/account.
- Full API docs: https://docs.sprites.dev/ (only needed if you go beyond the
snippet below).
- Sprite name rules: lowercase letters, digits, and hyphens only. Max ~63 chars.
- Auth: every request sends `Authorization: Bearer <SPRITES_TOKEN>`. Server-side
only — the token is a bearer secret, never ship it to the browser.
- Four primitives exist (create / fs.write / services / exec). This build uses
exactly one, wired end-to-end in the snippet below.
LOVABLE BUDGET (HARD CAP: ONE-SHOT, ~5 CREDITS TOTAL):
The participant has FIVE Lovable credits for the whole build. This prompt MUST
ship a working demo on the FIRST message with zero follow-ups. Engineer for that.
- ONE TanStack Start app, ONE route (`src/routes/index.tsx`). No extra pages, no auth, no nav.
- ONE TanStack server function in `src/lib/sprites.functions.ts` that proxies the Sprites call.
- ONE client surface (a textarea + launch button, or console box, or compose form) wired to it.
- NO database, NO Lovable Cloud, NO auth, NO file uploads to Lovable, NO extra integrations.
- NO tests, NO docs pages, NO settings screens, NO theming toggles.
- Libraries: template defaults + `zod`. Nothing else.
- Keep the diff small enough to land in one build pass. If a feature is not on
screen in the user flow below, do not build it. Cut scope before adding scope.
STACK
- TanStack Start app, the index route only.
- fly.io Sprites (api.sprites.dev/v1) is the only backend. All calls live inside a
`createServerFn` handler so `SPRITES_TOKEN` stays on the server.
- Client surface fits the primitive: a form/prompt that returns the sprite URL or stdout.
- Tailwind + shadcn. Editorial look: gold accent on a dark or warm-cream
background, generous type, one strong headline, one primary action.
- Footer renders: "Built during the Sprites Creative Hackathon organised by StreetKode Fam during Indian Krump Festival 14".
SERVER FUNCTION (src/lib/sprites.functions.ts) — one-shot exec inside a Sprite:
```ts
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!}` });
/** Built during the Sprites Creative Hackathon organised by StreetKode Fam during Indian Krump Festival 14 */
export const run = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({ script: z.string().min(1).max(4000) }).parse(d))
.handler(async ({ data }) => {
// Reuse a single sprite per session so exec is fast; create-if-missing.
const name = `studio-ergonomics-console`;
await fetch(`${API}/sprites`, {
method: "POST",
headers: { ...auth(), "Content-Type": "application/json" },
body: JSON.stringify({ name, url_settings: { auth: "public" } }),
});
// POST /exec with repeated ?cmd= params — Authorization ONLY, no Accept header.
const qs = new URLSearchParams();
qs.append("cmd", "bash");
qs.append("cmd", "-lc");
qs.append("cmd", data.script);
const res = await fetch(`${API}/sprites/${name}/exec?${qs}`, {
method: "POST",
headers: auth(),
});
if (!res.ok) throw new Error(`Sprite exec failed: ${res.status}`);
// Response stream ends with 0x03 <exitCode>.
const bytes = new Uint8Array(await res.arrayBuffer());
let exit: number | null = null;
let end = bytes.length;
if (bytes.length >= 2 && bytes[bytes.length - 2] === 3) {
exit = bytes[bytes.length - 1];
end = bytes.length - 2;
}
return { stdout: new TextDecoder().decode(bytes.slice(0, end)), exit };
});
```
CLIENT: script textarea prefilled with a studio ergonomics-appropriate one-liner + "Run". Render stdout
in a monospace pre with the exit code chip.
Do NOT add `Accept: application/octet-stream` on /exec — Sprites returns 406.
Do NOT create the sprite via PUT — it returns 404. Only POST /sprites creates.
USER FLOW (the entire app — nothing else exists)
1. Land on the page; the headline previews what the sandbox does for studio ergonomics.
2. The primary action (a prompt-driven console that runs a shell command in the sandbox and streams the output back to the user) is one tap away; the rest of the layout supports it.
3. Sprites runs the primitive server-side, the URL or stdout lands on screen, the user
can share, retry, or copy.
GOTCHAS (universal — apply to every Sprites call in this build)
- Token MUST be the 4-part `org-slug/org-id/token-id/token-value` from
sprites.dev/account. A raw Fly.io org token returns 401 authentication failed.
- Create is POST-only: `POST /sprites`. `PUT /sprites/{name}` returns 404.
- Services are PUT-addressed at `/sprites/{name}/services/{service}`. `POST`ing
the services collection returns 405.
- `http_port` is REQUIRED in the service PUT body. Omit it and the sprite shows
"Running" but every request 502s.
- Serve files from `/root/www`. `/home/sprite` may not exist — services fail to
start with `cd: No such file or directory`.
- `/exec`: send `Authorization` ONLY. Adding `Accept: application/octet-stream`
returns 406. The response body ends with `0x03 <exitCode>` — strip those two
bytes before decoding stdout.
- Cold boot: after `services/{name}/start`, warm-poll the public URL (up to
~12x @ 1s, 4s fetch timeout) before returning it, or the user's first click
hits a 502.
- Never call `api.sprites.dev` from the browser. Every fetch lives inside a
`createServerFn` handler so `SPRITES_TOKEN` stays server-side.
KEY — only ONE secret is required:
1. `SPRITES_TOKEN`. Grab the 4-part token (org-slug/org-id/token-id/token-value)
from https://sprites.dev/account and paste it into Project Settings ->
Secrets. Read it only on the server via `process.env.SPRITES_TOKEN`. Never
prefix with `VITE_`, never expose to the client. This single token unlocks
create, filesystem, services, and exec on api.sprites.dev.
CREDIT (must appear in UI footer AND as JSDoc on the server function):
Built during the Sprites Creative Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Market sizing.
Indicative figures for hackathon pitches — refine with your own research before raising.