For power users
Automation & API
Spiralyst Lab is scriptable. Every on-screen control has a programmatic twin, so a script, a MIDI/OSC bridge, an AI agent, or an automated QA harness can drive what the app shows and read what it hears — in real time, without faking mouse clicks.
Local only. The automation surface runs entirely on your Mac — no remote control, no telemetry, nothing leaves your machine.
This page documents the creative subset of the surface: read and set the whole scene, switch fractals and wire reactivity, inspect the live audio, run Auto-Reactivity, and export the rendered viewport to PNG.
Who it's for
- Power users who want to script setups and batch-export art.
- DJs & VJs bridging a MIDI or OSC controller to colors, motion, and fractal parameters.
- AI agents that read the live audio and current look, decide what should happen next, and apply it.
- QA & measurement: drive a known scene, render, read pixels + audio + state, and grade against a baseline.
How to drive it
Two paths into the same surface — the HTTP API for code (any language, any tool) and a set of reference Claude Code skills we publish here for anyone who wants a ready-to-install agent integration.
HTTP — http://127.0.0.1:<port>/api/*
The app's embedded localhost server. Drive it from any language — curl, a
Python script, a Node process, a MIDI/OSC bridge, an AI agent. Twenty-plus endpoints
across state, UI, window, audio, and export. Changes apply within one ~250 ms poll
tick.
Localhost-only, with a per-launch bearer
token — by design. The server binds 127.0.0.1 on an OS-assigned port,
and every request needs a secret token that's regenerated every time the app launches. The
port + token are written to a private file in your home folder, readable only by you — so
only software running as you can find them and call the API. Same model
Jupyter
and VS Code use. It is not a remote-control surface — don't expose it off the machine.
Read the port + token (written to ~/.spiralyst-lab/api-port.json at launch)
# the file lives at ~/.spiralyst-lab/api-port.json — 0600, user-only:
# { "url": "http://127.0.0.1:54321", "port": 54321, "token": "…", "version": "3.3.0", … }
P=~/.spiralyst-lab/api-port.json
BASE=$(jq -r .url "$P")
AUTH="Authorization: Bearer $(jq -r .token "$P")"
Both the port and the token rotate on every launch — re-read the file each run, don't hard-code either. The same two values are also shown in-app under About ▸ Spiralyst Lab with Copy buttons, for when a human wants to grab them without opening a file.
~/.spiralyst-lab/api-port.json on every launch — pick whichever path
your workflow prefers.
Quickstart
A complete drive-and-capture loop over HTTP: read the port + token, fetch the scene, switch fractal + enable audio, wait a tick, export a 4K still, and read where it landed.
# 1. read $BASE and $AUTH (see above) — one jq line each from ~/.spiralyst-lab/api-port.json
# 2. read the current scene
curl -s -H "$AUTH" "$BASE/api/state" | jq '{ type, hueStart: .color.hueStart }'
# 3. switch fractal + enable system audio
curl -s -H "$AUTH" -XPOST "$BASE/api/state/apply" -H 'Content-Type: application/json' \
-d '{"type":"fractal-phyllotaxis","audio":{"enabled":true,"source":"system"}}'
# 4. wait ~0.3s for the JS poll to apply
sleep 0.3
# 5. export a 4K still
curl -s -H "$AUTH" -XPOST "$BASE/api/export/png" -H 'Content-Type: application/json' -d '{"res":"4K"}'
# 6. read where it was written
curl -s -H "$AUTH" "$BASE/api/export/png" | jq '.path' # → ~/.spiralyst-lab/qa/<file>.png
Every request needs the Authorization: Bearer … header
— a request without the right token returns 401 Unauthorized.
Reference — creative subset
Path · method · purpose · params, with the window.__spiral.*
equivalent. This is the creative subset only; see Full reference
for advanced/agent endpoints.
State
| Endpoint | Purpose | Params | JS equivalent |
|---|---|---|---|
| GET /api/state | Last pushed scene snapshot (or null at boot before the first push). | — | __spiral.state.get() |
| POST /api/state/apply | Apply a full scene snapshot (within ~250 ms). | snapshot blob (JSON body) | __spiral.state.set(snap) |
| POST /api/state/undo | Step back in history. | — | __spiral.undo() |
| POST /api/state/redo | Step forward in history. | — | __spiral.redo() |
| GET /api/state/stack | { undo, redo, canUndo, canRedo } | — | __spiral.stack() |
UI
| Endpoint | Purpose | Params | JS equivalent |
|---|---|---|---|
| POST /api/state/tab | Switch the sidebar tab. | { name: "source" | "visual" | "camera" | "export" } | __spiral.ui.tab(name) |
| POST /api/state/scroll | Scroll a control into view (auto-activates its tab). | { target: "color:hueStart" } | __spiral.ui.scrollTo(target) |
Audio
| Endpoint | Purpose | Params | JS equivalent |
|---|---|---|---|
| GET /api/audio/snapshot | Latest audio-input frame: { source, active, peak, rms, spectrum[32], pitchHz, ... } — the values the artwork reacts to. | — | __spiral.audio.snapshot() |
| GET /api/audio/bands | Live per-reactive-slider levels (incl. saturated / lowDynamics flags) — see whether a band is maxed out or barely moving. | — | — |
| POST /api/audio/heal | Run Auto-Reactivity (AGC). Runs in-app over a few seconds; poll GET /api/audio/heal for the report. Needs music playing. | { policy?, keys?, targetPeak? } | await __spiral.audio.heal({ policy: "auto" }) |
Export
| Endpoint | Purpose | Params | JS equivalent |
|---|---|---|---|
| POST /api/export/png | Render the active viewport to a PNG (exact pixels, not a screen grab). Writes to ~/.spiralyst-lab/qa/. | { res: "4K" | "1440p" | "1080p" | "720p" } or { w, h, ppi } | await __spiral.export.png({ res: "4K" }) |
| GET /api/export/png | The last export report: { path, w, h, ppi, bytes } — poll after the POST. | — | — |
Output location. Exported PNGs are written
to ~/.spiralyst-lab/qa/. POST /api/export/png enqueues the render;
GET /api/export/png returns the report with the path once it lands.
Examples
From the WebView devtools console — window.__spiral (instant)
__spiral.state.patch({ type: "fractal-phyllotaxis", audio: { enabled: true, source: "system" } });
__spiral.state.patch({ color: { hueStart: 0.9 } }); // set one field → re-renders
await __spiral.audio.heal({ policy: "auto" }); // tune reactivity to the music
await __spiral.export.png({ res: "4K" }); // → ~/.spiralyst-lab/qa/
Switch a fractal + tune reactivity over HTTP
# enable system audio + phyllotaxis
curl -s -H "$AUTH" -XPOST "$BASE/api/state/apply" -H 'Content-Type: application/json' \
-d '{"type":"fractal-phyllotaxis","audio":{"enabled":true,"source":"system"}}'
# auto-tune, then poll the report (music must be playing)
curl -s -H "$AUTH" -XPOST "$BASE/api/audio/heal" -H 'Content-Type: application/json' -d '{"policy":"auto"}'
sleep 4
curl -s -H "$AUTH" "$BASE/api/audio/heal" | jq '.healed'
Inspect the live audio (what the artwork hears)
curl -s -H "$AUTH" "$BASE/api/audio/snapshot" | jq '{ peak, rms, pitchHz }'
curl -s -H "$AUTH" "$BASE/api/audio/bands" | jq '.bands[] | { key, level, saturated, lowDynamics }'
Full reference
This page covers the creative subset only. The complete contract — file read/write, save/open dialogs, the calibration suite, and the full 50+ endpoint list for advanced and agent use — lives in the agent-control reference, hosted here for direct read access from agents, scripts, and integrations.
Reference — Claude Code skills
Four reference skills exist for Claude Code
(Anthropic's CLI agent). Claude Code resolves slash commands like /spiral-observe to
a local skill folder under ~/.claude/skills/<name>/; the skill's
SKILL.md tells Claude what the command does and how to drive it.
Not bundled. Spiralyst Lab does not ship these skills, and they are not installed by Claude Code. They are reference examples, published here, demonstrating how to drive the API above. Install them yourself with the steps below, or read them as worked examples and write your own. Nothing about the API is Claude-specific — any agent that can speak HTTP can drive Spiralyst Lab.
/spiral-observe— read the live audio (snapshot, per-band reactivity, full-rate frames), read the scene state, capture a PNG of the rendered viewport, and report what the app is currently doing. [SKILL.md]/spiral-scene— build a scene programmatically from a natural-language description: fractal type + params, effects, layers, background, and wire sliders to audio bands or sine animation, then verify with a screenshot. [SKILL.md]/spiral-calibrate— run the calibration suite (audible by design): play ground-truth tones, grade pitch / gain / beat timing / settings round-trip / visual render against a baseline. [SKILL.md]/spiral-heal— auto-tune (AGC) the audio-reactive bands so sliders stop pinning at max or sitting at zero. Tunes gain, squelch, and signal together. [SKILL.md]
Setup
- Install Claude Code and sign in.
- Create the skills directory if it doesn't exist:
mkdir -p ~/.claude/skills - For each skill, create a folder and drop in a
SKILL.md. Withcurl:
Eachfor name in spiral-observe spiral-scene spiral-calibrate spiral-heal; do mkdir -p ~/.claude/skills/$name curl -fsSL "https://spiralyst.com/skills/$name/SKILL.md" \ -o ~/.claude/skills/$name/SKILL.md doneSKILL.mdis the literal file the agent reads — open the [SKILL.md] links above to inspect the source before you install. - Restart Claude Code. The four slash commands resolve on next session start.
- Launch Spiralyst Lab (the skills drive the app's local HTTP server documented above; if the app isn't running there's nothing to talk to).
Inspect before you install. Each
skill is a single Markdown file with a YAML frontmatter (name,
description) and a body of plain instructions plus shell snippets that call
the public HTTP API documented above. Read the source from the
SKILL.md links next to each skill name —
nothing is obfuscated, and there are no hidden binaries or daemons.
Caveats
- Localhost only. The HTTP server binds
127.0.0.1— it is not a remote-control surface. Don't expose it off the machine. - Latency. HTTP changes apply within one ~250 ms poll tick. The
window.__spiralobject (accessible via the app's WebView devtools console — Develop → Web Inspector → Console) is synchronous and instant; it's documented in the reference table above for developers who want zero-latency control without leaving the app's runtime. - One instance. Drive a single running app instance at a time; the port is per-launch and OS-assigned.