- TypeScript 79.7%
- CSS 13.8%
- Astro 4.2%
- JavaScript 1.1%
- Go Template 0.5%
- Other 0.7%
120000ms covers a standalone `celilo module run-hook` (~71s measured) but not the DEPLOY path, which also publishes the static site and issues dns_registrar.registerHost twice before it reaches public_web.registerReverseProxy. Measured 2026-09-29 (celilo#1463): the deploy of 0.6.0+1 died with "Hook total timeout exceeded (120s), waiting on public_web.registerReverseProxy (44s)". The module was left DEPLOYED with its /live reverse proxy never registered, so /live/trpc fell through to the apex static site. A hand-run `run-hook` finished in 71s and registered it. No changeset: this leaves the manifest's declared version alone so the release publishes directly (the detect step sets should_publish=true only when no changesets are pending), and the registry's +N build counter still advances. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .beads | ||
| .claude | ||
| .cursor/rules | ||
| .forgejo/workflows | ||
| app | ||
| docs | ||
| e2e | ||
| electron | ||
| openspec | ||
| projects | ||
| scripts | ||
| src | ||
| website | ||
| .envrc | ||
| .gitignore | ||
| AGENTS.md | ||
| bun.lock | ||
| CLAUDE.md | ||
| CONTEXT.md | ||
| GEMINI.md | ||
| globals.d.ts | ||
| lasertag.d.ts | ||
| mise.toml | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| tsconfig.json | ||
incarnadine
An alt-tango DJ app for macOS. Two modes:
- Building mode — a Claude skill (
.claude/skills/tango-library/) imports your Spotify playlists into a local SQLite library and enriches each track with tango metadata (traditionality, energy, romance, juiciness, BPM) for your review. - Playing mode — a local web console that drives the Spotify desktop app via AppleScript: pick from scored candidate cards, manage the queue, auto-advance with a hard gap (never a crossfade), capture notes, watch set stats.
Requires: macOS, Spotify Premium, the Spotify desktop app, Bun.
Setup
bun install
1. Spotify app credentials (building mode only)
Playing mode needs no Spotify API auth. To import playlists you need a Spotify app:
- Create an app at https://developer.spotify.com/dashboard
- Add
http://127.0.0.1:8988/callbackas a Redirect URI - Export the client id:
export SPOTIFY_CLIENT_ID=...(or put it in.env)
The first bun run library import ... opens a browser for a one-time OAuth
(PKCE) grant; the token is cached in .spotify-token.json.
2. Spotify desktop app settings
In Spotify → Settings:
- Disable Autoplay ("Autoplay similar songs when your music ends") — otherwise Spotify may start an unrelated track in the gap before this app advances.
- Disable Crossfade — hard gaps between songs are the point. Each song plays
to its end, then the app holds a few seconds of silence (
POST_TRACK_GAP_Sinsrc/playback/engine.ts) before firing the next one.
3. First-run AppleScript permission
The first time the server sends a command to Spotify, macOS prompts to allow Terminal (or whatever runs Bun) to control Spotify. Do this before an event:
osascript -e 'tell application "Spotify" to player state'
Approve the automation prompt (System Settings → Privacy & Security → Automation if you need to fix it later).
4. Run a set
bun start # runs under caffeinate so the Mac won't sleep mid-set
Open http://localhost:3000, start the Spotify desktop app, and DJ.
(Port taken? PORT=3210 bun start. Different library file? TANGO_DB=path.)
Spotify grabs focus when a new track starts; the app gives focus back automatically. To keep Spotify off-screen entirely, hide it with ⌘H (not minimize) — the app re-hides it after every track change.
Building the library
Ask Claude to use the tango-library skill, or run the helper directly:
bun run library import <playlist-url-or-id> # import a playlist
bun run library sync # re-walk everything imported
bun run library list-playlists # completeness report
bun run library export # mirror library → one playlist
bun run library list --needs-review # tracks awaiting review
bun run library set <uri> energy 7 # set a value (Claude enrichment)
bun run library confirm <uri> energy 4 # DJ correction — never overwritten
bun run library note <uri> claude "text" # attach a note
Before an event: offline readiness
A venue with bad internet can kill a set. Spotify Premium plays downloaded tracks offline, but only per playlist — so mirror the whole library into one playlist and download that:
bun run library sync # re-walk every playlist; recovers anything a flaky
# connection left half-imported
bun run library export # mirror every non-excluded track into one playlist
export creates (or updates) a private playlist called Incarnadine DJ
Library and prints its link. In the Spotify app, open it and switch
Download on, then wait for the green arrow — Spotify exposes no API or
AppleScript for download state, so that check is yours to make.
Re-run export after importing or excluding tracks; it re-diffs every time,
so the playlist always matches the library. That also means hand edits made
in Spotify get reverted on the next run.
To see whether anything is missing:
bun run library list-playlists
Every playlist you've imported is listed with a status: ok, partial (a
fetch died partway — sync will finish it), error, or forbidden (Spotify
refuses other owners' playlists to new apps; import-embed gets ~100 tracks
of those, and sync skips them unless you pass --force).
Online companion (local)
Guests follow a set and send requests at /live, served by the dj-live app
server (app/server). To run the whole thing on one machine:
# App server in test mode (self-signed DJ tokens). Real Spotify search when
# SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET are in .env, else a fake catalog
bun run live:dev
# Console pointed at it (SPOTIFY=fake plays tracks without the Spotify app)
LIVE_URL=http://localhost:3100/live SPOTIFY=fake bun run dev
The guest site is at http://localhost:3100/live. "Go online" in the console
signs in through the IdP; in test mode, store a self-signed DJ token in the
console's settings instead (live.token and live.tokenExpiresAt), as
e2e/seed.ts does.
Tests
bun run test # unit tests: src, app, scripts
bun run check # tsc + lasertag
bunx playwright install chromium # once
bun run e2e # Playwright: app server + console with fake Spotify, phone + desktop
bun run e2e starts its own app server (port 3400) and console (port 3300)
with a seeded library, so it runs alongside a dev console.