Developer docs.

Run your own Baste installation, configure generation services, or build with the CLI and API.

Looking for help with personas, materials, moodboards or exports? Read the Studio guide.

Run locally

From a checkout with Nix and Devenv installed, enter the development shell, install the locked dependencies, then start the API and site together.

devenv shell
devenv tasks run baste:install
devenv up

The Studio runs at localhost:5173/gui/. Its same-origin /api proxy reaches the Baste server on port 3456. Set BASTE_SITE_PORT and BASTE_API_PORT to change the ports. Storybook is an optional process on port 6006.

Use the native tasks for individual commands and checks. The full task list and environment setup live in the development guide.

Configure an account server

In Studio Settings, select Sign in / create profile. Not Organic creates or authenticates your account profile; a Baste design persona is a separate object. Manage your handle and account in the linked Not Organic portal.

The Baste server holds the provider session and DPoP key. The browser receives an HttpOnly Baste session cookie, a CSRF token and the profile's public DID and handle. The model picker loads the authorized catalog from your connected account.

Your wallet shows verified available credit and only the offers advertised by the provider. Select an offer to open checkout and review its current price before paying. On return, refresh the wallet: a redirect alone does not prove payment or credit delivery.

Hosted generation covers image suites and asset judging. When the provider enables its fal roles, editing, segmentation and image animation use the same account credits. Full UI kits still need a local SVG provider. An unverified, blocked or empty wallet prevents paid hosted generation; prompt drafts remain available.

Set a maximum spend for each hosted run, initially $1. The live budget plan counts image and judging requests, plus selected edits, segmentation attempts and animation jobs. Reduce iterations, batch size or outputs per purpose until the plan fits your maximum and the server limit, or explicitly raise your maximum. These conservative ceilings are spending limits, not quoted prices.

For server operators: enable NOTORGANIC_ENABLED, set BASTE_PUBLIC_ORIGIN, and configure the issuer and authorization URL. Baste must be registered with Not Organic for its origin, callback, product audience, scopes and billing offers. Public checkout needs an HTTPS return origin.

NOTORGANIC_ENABLED=true
BASTE_PUBLIC_ORIGIN=http://localhost:5173
NOTORGANIC_ISSUER=https://api.notorganic.info
NOTORGANIC_AUTHORIZATION_URL=https://id.notorganic.info/authorize

The callback is /api/notorganic/callback on the Studio origin. Production hosting must proxy /api to the Baste server. Sessions live in server memory and expire after eight hours; restarting the server requires signing in again. Read the account integration guide for the deployment boundary.

Use your own provider keys

Local mode keeps provider keys in the server environment or local config. Image generation uses OpenAI or Google; SVG generation uses QuiverAI; video uses its configured provider. The Studio never needs an upstream key pasted into a form.

OPENAI_API_KEY=…
QUIVER_API_KEY=…
GOOGLE_API_KEY=…

Copy .env.example to .env and configure only the services you use. Run baste config --init to create baste.config.json. Model IDs must be supported by your chosen provider. Local generation uses this configuration; hosted Studio and CLI requests use their connected account sessions.

Run everything on one fal key

With FAL_KEY set, one fal account can cover a whole run: Seedream v4 images, Recraft v3 vector icons, Seedance 1 Pro ambient loops, and a judge that looks at the rendered images through fal’s vision model. The archive in the tour came from a run like this.

FAL_KEY=… baste generate cyberbotanist --provider fal

# Mix providers: fal images, your QuiverAI SVGs, OpenAI judging
baste generate cyberbotanist --image-provider fal --svg-provider quiver --judge openai

--provider fal routes every stage through fal; --image-provider, --svg-provider, --video-provider and --judge override one stage each. The vision judge defaults to google/gemini-2.5-flash; SVG markup and video prompts go to the text endpoint because the vision endpoint needs an image. In the Studio, choose fal (images, icons and judge) as the image provider. A run fails at the start when FAL_KEY is missing, before anything is spent.

Draft with --dry-run first. Each stage bills your fal account, and judging adds a request for every candidate, so iterations and batch size multiply the cost.

Let Jev look

With TYPESAFE_API_KEY set, TypeSafe’s Jev judges every candidate and final asset, and it now judges what was rendered rather than the prompt. It sees each image, every SVG on light and dark grounds at 64, 32 and 16 px, and six frames sampled across each video loop. Beside them it gets up to four pictures of the persona’s world: moodboard pins, baste culture images, and a decomposed site’s share image and palette. It also sees the suite’s latest finished asset of each purpose, and the assets you upvoted and downvoted, which become pictured examples for a taste judgment.

TYPESAFE_API_KEY=… baste generate cyberbotanist --judge typesafe

# Prompts and SVG source only, no images
baste generate cyberbotanist --judge typesafe --text-judge

In the same request, Jev reads the archive’s colour temperature, density and rendering off each image, so a candidate lands where it looks rather than where its prompt said it would. For images and loops, text laid over the picture (a caption, watermark or interface label) rejects the asset. Signs that belong to the scene do not. For loops, Jev names the motion it sees among the four motion languages, checks that the last frame flows into the first, and rates how calm the loop sits behind interface content. A miss becomes an improvement note, not a rejection.

Images are a TypeSafe preview feature: Baste sends the opt-in header, fits every picture inside TypeSafe’s size limits and uploads it as lossless PNG, so compression artifacts are never judged as the asset’s. Video frames, WebP and GIF need ffmpeg on the server; without it, those assets are judged on their prompt. If TypeSafe refuses an image request, that asset is judged again as text and tagged vision-refused. Hosted Not Organic judging stays text-only. Each result records what Jev saw as evidence: text-only, image or frames.

Motion primitives

A persona’s motion style (smooth, snappy, liquid or mechanical) is one motion language. It writes the motion tokens, the motion line in video prompts, and the options Jev chooses between when it reads a loop’s frames. Each language has semantic curves (standard, enter, exit, emphasis), a physical spring exported as a CSS linear() curve, travel distances, a press scale and a stagger interval. Denser personas move less and sequence faster.

baste tokens nightmarketcoder --format css
# writes assets/output/nightmarketcoder-tokens.css

<ul class="motion-stagger">  <!-- children enter one after another -->
  <li class="motion-hover motion-press">…</li>
</ul>
<span class="motion-emphasis">Saved</span>
<div class="motion-ambient"></div>

The CSS export adds @keyframes and .motion-enter, .motion-exit, .motion-hover, .motion-press, .motion-emphasis, .motion-ambient and .motion-stagger classes. Under prefers-reduced-motion, enters and exits only fade, and travel, emphasis and loops stop. Tailwind and Panda get the same curves, durations and keyframes; JSON carries the full choreography as Web Animations keyframes for animation libraries. The Studio’s Tokens tab plays each move.

Edit, layer and animate

For hosted credits from the CLI, run baste login, then baste credits to inspect your balance and available offers. Use baste credits --buy <pack-id> to open checkout, and verify your balance after payment. Generate with --provider notorganic --max-cost 5; reduce iterations and outputs if the spending plan exceeds that limit. The CLI’s loopback client must be registered by Not Organic. Use baste logout to revoke this device.

Creating and editing are separate roles. Your image model draws each candidate. With a local FAL_KEY or the signed-in account’s enabled fal roles, SAM 3 cuts the persona’s subject out by name, Ideogram 3 repaints the region around or inside it, and Seedance turns a finished image into a loop.

# Repaint candidates during the search; cut final images into layers
baste generate nightmarketcoder --edit

# Parallax from the layers, a video loop, or both
baste generate nightmarketcoder --animate both

With --edit, about half of each generation’s new candidates repaint a kept image one step along the archive’s axes instead of drawing anew, and Jev judges them like any other candidate. The final step ships the image the search judged. Each final image becomes three layers: the subject on transparency, the scene with the subject painted out, and the mask. Jev picks which of the persona’s subjects to cut, then checks the cut is whole and the scene clean.

Animation follows the persona’s animation block: what moves, the subjects to cut out, depth, camera, focus, loop length and motion to avoid. The parallax loop ships as a script-free HTML page, a JSON spec and, with ffmpeg, an MP4 that loops exactly. The video loop is played forward and back. When the persona’s focus region is small enough to keep the composition, Seedance animates towards an end frame Ideogram paints. Hosted runs can’t edit or animate yet.

Companion voice

Set FAL_KEY on the Baste API server for ElevenLabs Turbo v2.5 speech and Wizper transcription. The browser sends recorded audio to Baste and receives audio bytes or editable text; it never receives the provider key. Recording starts only after a user gesture and stops after 45 seconds.

node scripts/runtime.mjs api --voice-from-skate

The Skate option reads fal_api_key@secrets into the API process environment. Alternatively set FAL_KEY in your server environment. The fal account needs available credit. Set BASTE_VOICE_ENABLED=false to disable voice.

Hosted account mode additionally requires BASTE_VOICE_ENABLED=true. Existing account authentication, origin and CSRF checks apply. Voice uses the operator’s fal balance, separately from Not Organic wallet billing. Each server process allows ten requests per caller per minute, 120 total per hour, and four concurrent requests; replicated deployments need a shared limit before increasing capacity.

Speech and transcription use bounded, cancellable requests. Audio is not saved by Baste. fal receives the audio and text, with provider request storage disabled. Spoken replies require an explicit opt-in. Pause, resume, stop, persona changes and tab visibility control playback and microphone cleanup.

This is turn-based voice with editable transcripts. The planned GPT-Live design partner remains a separate integration. Voice does not turn the authored guided demo into a model conversation.

CLI reference

The CLI operates on your local persona store. It does not inherit a Studio account session. Use baste --help for versioning, remix, site decomposition, cultural references and OpenPencil commands.

CommandPurpose
baste listList base and custom personas
baste show cyberbotanistRead a persona and its cultural influences
baste create my-persona --base cyberbotanistMake a custom persona from a base
baste generate cyberbotanist --dry-runInspect the brief without paid provider calls
baste generate cyberbotanistGenerate an asset suite with configured providers
baste ui-kit cyberbotanistGenerate a full kit, including local SVG and video providers
baste generate cyberbotanist --text-judgeJudge with Jev on prompts and SVG source only, without images
baste generate cyberbotanist --edit --animate bothRepaint candidates with Ideogram, cut final images into layers and animate them (fal)
baste tokens cyberbotanist --format cssExport tokens and motion primitives as CSS, JSON, Panda or Tailwind
baste gui --port 3456Run the API and bundled GUI
baste config --initWrite a starting configuration
baste mcp --stdioExpose Baste tools through MCP

API reference

The development site proxies these same-origin routes to the local API. Account routes require a connected session. In account mode, personas and generation results are scoped to the authenticated DID. Mutations require X-Baste-CSRF from the status response.

RoutePurpose
GET /api/healthServer health
GET /api/personasAvailable personas
POST /api/personasCreate a design persona
GET /api/personas/:idRead a persona
PUT /api/personas/:idUpdate a custom persona
POST /api/tokens/:idExport tokens; JSON body: { format }
POST /api/prompts/:idPreview the image, SVG and video briefs
POST /api/generate/:idStart a job; JSON body: { dryRun, imageProvider, imageModel }
POST /api/generate/:id/planCheck image/judging call ceilings and the required per-run budget
GET /api/jobs/:idRead progress, result or failure
GET /api/assets/images/:filenamePreview or download an image owned by the signed-in account
GET /api/notorganic/statusRead account configuration and CSRF token
POST /api/voice/speak/:idSpeak a companion reply; JSON body: { text }, maximum 1600 characters
POST /api/voice/transcribeTranscribe recorded audio; audio/webm, audio/mp4, audio/wav or audio/mpeg, maximum 1 MiB
POST /api/notorganic/loginStart profile creation or sign-in
GET /api/notorganic/modelsAuthorized live hosted model catalog
GET /api/notorganic/walletVerified balance and available checkout offers
POST /api/notorganic/checkoutGet payment URL for an advertised packId or planId
POST /api/notorganic/logoutEnd the Baste account session

Errors distinguish authentication (401), denied access (403), insufficient credit (402), busy provider (429), and unavailable services. Config writes and native file/watch operations are unavailable in account mode. The README contains the wider local API and TypeScript package usage.

Keep the fitting

Local output lives under assets/output/: SVGs, images, videos, plus a persona suite manifest with metadata and relationships. In account mode, persona records and assets belong to the authenticated DID's separate directory.

assets/output/
  svg/
  images/
  videos/
  {persona}-suite.json

The quality-diversity archive explores color temperature, visual density and abstractness. Judging weights persona alignment at 30%, visual quality and uniqueness at 20% each, coherence and usability at 15% each. These scores guide selection; review assets in the interface they will serve.

Rebuild the tour video

The 74-second tour on the home page and in the Studio guide is a HyperFrames composition in videos/baste-tour/. Every screen in it is recorded from this site, and the archive replays a real fal run. Each stage writes files the next one reads, so you can redo one step without the others.

# 1 · Record the screens (site on :5173, system Chrome on the GPU)
node scripts/record-tour.mjs

# 2 · Narration on Cartesia, word timings and music on fal
node scripts/narrate-tour.mjs --skate --keep-timing
node scripts/score-tour.mjs --skate

# 3 · Compose, check, render, publish
cd videos/baste-tour
node build.mjs
npx hyperframes@0.8.145 check
npx hyperframes@0.8.145 render --quality high --fps 30 --output renders/baste-tour.mp4
node publish.mjs

build.mjs holds the edit: which seconds of each recording play, at what speed, and where the camera moves. It writes the composition, levels the voice lines, and makes English and French captions; the French lines are in narration/captions.fr.json. publish.mjs encodes the web video, poster and the Studio guide’s chapter clips into site/public/media/tour/, and writes site/src/lib/tour.json for the chapter list and transcript.

Narration is Cartesia Sonic in the Theo voice (scripts/cartesia.mjs); word timings and music come from fal. The scripts read CARTESIA_API_KEY and FAL_KEY, or take both from Skate with --skate; keys are never written to a file. On a Nix or Guix host, run the recorder and the HyperFrames commands with env -u LD_LIBRARY_PATH so Chrome loads the system libraries, and put the system ldd first on PATH for the render preflight. A render is cancelled when its launching shell exits; set HYPERFRAMES_RENDER_DETACHED=1 to run it detached.