---
name: spiral-observe
description: Observe the live state of Spiralyst Lab — read the audio feed (per-band reactive levels, full-rate frames), the scene state, and capture+read a screenshot of the rendered art. Invoke when the user says /spiral-observe or asks what the app is currently doing, whether audio is coming through, whether a band is saturating, or to QA the current visual.
---

# Spiralyst Lab — observe

Spiralyst Lab exposes a local HTTP API on `127.0.0.1` (loopback only). Every
request requires a per-launch bearer token. The port and token are written to
`~/.spiralyst-lab/api-port.json` at launch (mode `0600`, readable only by the user)
and both rotate every time the app launches — re-read the file each run.

## Read the port + token

```
P=~/.spiralyst-lab/api-port.json
BASE=$(jq -r .url "$P")                              # http://127.0.0.1:<OS-assigned port>
AUTH="Authorization: Bearer $(jq -r .token "$P")"    # per-launch token
```

If `$P` doesn't exist, ask the user to launch Spiralyst Lab. (The same `url` and
`token` are mirrored under About ▸ Spiralyst Lab in the app, with Copy buttons.)
Every request without the right token returns `401 Unauthorized`.

## Read the audio (the values the art reacts to)

- `GET /api/audio/status` — is the tap active? sample rate? frame count?
- `GET /api/audio/snapshot` — the latest analysis frame:
  `{peak, rms, pitchHz, centroidHz, flux, spectrum[32], ...}`. Confirms signal flow
  and content.
- `GET /api/audio/bands` — per-band reactivity values the visuals are actually using,
  with `saturated` and `lowDynamics` flags. This is how you see a band pinned at the
  top or sitting steady. **Not** visible through `/api/state` (that mirror is frozen
  for animated values).
- `GET /api/audio/frames?since=0` — recent analysis frames at full rate (~94 Hz) for
  beat / latency work.

## Read the scene

- `GET /api/state` — full scene snapshot: fractal type and params, color, effects,
  layers, per-slider modes and audio-band configs. Note: animated and reactive
  *output* values here are stale (push-on-edit); use `/api/audio/bands` for live
  reactivity values.

## Read the pixels (visual QA)

Export the rendered viewport to PNG (exact pixels, any resolution, no screen grab):

```
curl -s -H "$AUTH" -XPOST "$BASE/api/export/png" \
  -H 'Content-Type: application/json' -d '{"res":"4K"}'
sleep 0.6
curl -s -H "$AUTH" "$BASE/api/export/png"   # → {path, w, h, ppi, bytes, at}
```

Then read the PNG at the returned `path` to grade the render. Omit the body for
native size, or pass `{"w":2560,"h":1440}` for a custom size.

For multi-frame motion capture, poll `/api/export/png` in a loop with a short sleep
between calls (e.g. 3 frames at 80 ms apart for velocity, 5–10 at 50 ms apart for
accurate motion).

## What to report

Tie it together: is audio active and what's in it, are any bands saturating or steady
(name them), what type and params are rendering, and what the screenshot shows. Flag
mismatches (e.g. "audio strong but band pinned → suggest /spiral-heal").
