What you're playing, in your Claude Code status line.
Whatever you're playing it with.
♪ Sault — Wildfires · Spotify · selecta
Put Spotify on. Install selecta. That line appears under your input box and stays current.
No key, no config, no dependencies, nothing to run. It reads the music app you already have open — Spotify, Apple Music, or anything speaking MPRIS on Linux.
Then, if you want music without leaving the terminal, it can play that too.
You already play music while you code. Two things you can't do: see what's on without leaving the keyboard, and ask a repository what it sounded like.
selecta does the first with nothing installed. For the second it keeps a crate per repository — the stations you played, weighted by how long you listened, every track stamped with the branch and commit that were checked out while it played.
demo-api 13h 23m · 904 tracks · since 2026-06-12
Groove Salad ██████░░░░ 8h 21m
Drone Zone ██░░░░░░░░ 3h 10m
Mission Control █░░░░░░░░░ 1h 51m
On repeat Alex Cortiz — Barista Breaks (14×)
First track Tycho — Awake, on 2026-06-12 while you wrote de0fe76
selecta resume → Groove Salad
Come back to a project after a month and selecta resume puts it back on.
/plugin marketplace add azandabot/selecta
/plugin install selecta@azandabot-selecta
/selecta:statusline on
That's the whole thing. It asks before touching your settings, and it shows you the exact JSON first.
Want it to play music too? brew install mpv jq, then:
/selecta:selecta play ambient
The point of the split: the status line costs you nothing, and everything below it is opt-in.
| You want | You need | Opens a window |
|---|---|---|
| See what Spotify / Apple Music is playing | nothing | no |
| Same, on Linux | playerctl |
no |
| Play radio from the terminal | mpv, jq |
no |
| Per-repo crates and history | jq |
no |
| Play one specific named song | the selecta-youtube plugin, a free API key |
yes |
selecta doctor checks every row and prints the exact install command for your
platform. It will not run a package manager for you.
selecta is the status line, radio, and the crate. No key, no window.
selecta-youtube adds "play this exact song by this exact artist". It needs
a free Google API key and opens a small visible browser window, because
YouTube's Developer Policies forbid playing from a player that isn't displayed.
Most people don't want that, so it's a separate install:
/plugin install selecta-youtube@azandabot-selecta
Once it's installed, selecta play "kabza de small - sponono" routes to it on
its own. Without it, selecta says so and plays radio instead.
Ask in your own words — "put something calm on", "what's playing?", "what has this repo been listening to?" — or type them:
| Command | What it does |
|---|---|
/selecta:selecta |
What's playing |
/selecta:selecta play <mood> |
Resolve and play on radio |
/selecta:selecta play |
Resume what this repo sounds like |
/selecta:selecta pause · go · next · stop |
Control whatever is playing |
/selecta:selecta vol 40 |
Also +10, -10, mute, unmute |
/selecta:crate |
What this repo sounds like |
/selecta:crate --history [n] |
Tracks grouped by the commit they scored |
/selecta:statusline on|off|status |
The status line |
/selecta:doctor |
Dependency and health check |
/selecta-youtube:youtube <track> |
One named song, in a window |
selecta uninstall [--purge] removes it cleanly.
/selecta:statusline on
Renders right-aligned under your input box, at three widths depending on your terminal, and disappears when nothing is playing.
Two things it tells you before writing anything:
- A plugin cannot ship a status line, so this edits your global
~/.claude/settings.json. It shows the exact JSON, backs the file up, and asks first. Nothing is written until you say yes. - Setting a status line hides most of the built-in footer key hints.
If you already have a status line, yours is kept and runs first; selecta adds a
second row. statusline off restores everything exactly as it was.
It forks nothing on the common path: no jq, no git, no subprocesses. It
reads one pre-rendered file, so a two-second refresh stays free. Every failure
path exits 0 — a fault in selecta must never break your status line.
| Platform | Reads | How |
|---|---|---|
| macOS | Spotify, Apple Music | AppleScript, only when the app is already running |
| Linux | anything MPRIS | playerctl |
On macOS the first probe triggers an Automation permission prompt. Deny it and
selecta stops asking, permanently, and doctor tells you how to re-enable it.
It never launches a music app that wasn't already open.
| Source | Key | What it gives |
|---|---|---|
| SomaFM | no | 46 curated, ad-free, listener-supported stations |
| radio-browser | no | thousands of community-indexed stations |
| YouTube | free, yours | any specific song by name, via selecta-youtube |
Nothing is downloaded. Streams are played, never stored.
SomaFM is listener-supported. If you use it, support them.
Only needed for selecta-youtube. Radio needs nothing.
The order matters, and getting it wrong is the single most common mistake: a key created before the API is enabled will exist and return 403 on every search.
- https://console.cloud.google.com → create or pick a project.
- Enable the API first. https://console.cloud.google.com/apis/library/youtube.googleapis.com → check the project is right → Enable → wait for it to finish.
- Now create the key. APIs & Services → Credentials → Create credentials → API key.
- Optional: Edit API key → API restrictions → YouTube Data API v3.
/selecta:selecta config youtube.api_key YOUR_KEY
Then selecta-youtube doctor, which asks Google rather than reading your config
file:
key set, 100 of 100 searches left today
api YouTube Data API v3 reachable and enabled
If step 2 was skipped it says api NOT ENABLED instead of pretending the key is
fine.
| Action | Cost |
|---|---|
| Playing, pausing, skipping, volume | free — playback uses no API |
| Replaying something already in your crate | free |
| Repeating a search from the last 7 days | free — cached |
| Radio, in any amount | free — no key involved |
| A new search phrase | 1 of 100 |
100 distinct new searches a day, not 100 plays. Resets midnight Pacific.
One long-lived supervisor polls every two seconds: it owns mpv when radio is
playing, reads whatever external player is running when it isn't, and is the
only writer of durable state. The CLI is a thin client over mpv's IPC socket.
Resolution is tiered, and the first two work offline:
0 exact station id, title or your own alias offline
1 mood vocabulary and catalogue scoring offline (46-station seed baked in)
2 radio-browser keyless
Crates live in ~/.claude/selecta/, keyed by repository. ssh and https remotes
collapse to one key, worktrees share the parent's crate, and a fork whose remote
later points upstream carries its history across.
Nothing is ever written into your repository.
Stated plainly, because finding these out yourself is worse.
- Over SSH, only the status line is useful.
mpvwould play on the remote host, which has no speakers, and there's no local music app to read. - selecta can see Spotify and Apple Music, but not control them.
pauseandvolreach selecta's own playback and the YouTube window. It says so rather than silently doing nothing. The one exception: starting selecta pauses whatever is already playing, so you never get two streams at once. Turn that off withselecta config playback.pause_others false. - YouTube mode always shows a window. Required by YouTube's policies, not a
design choice. It is a singleton: playing something else reuses or replaces
it, and
selecta stopcloses it. - Some official uploads refuse embedded playback. selecta skips to the next candidate, which can mean a re-upload or lyric video instead of the official one. When everything is blocked it hands you the direct link.
- Live radio cannot skip a track.
selecta nextchanges station, and says so rather than pretending. - Track titles depend on the station. Many streams send them; some send only the station name. selecta shows what it's given, never a guess.
- Music stops when your session ends. Deliberate: an orphaned audio process you can't find is worse than pressing resume.
- One player per machine, not per session. A second session shows the same track.
- macOS and Linux. Windows and WSL are not tested.
- Some stations play a short ident before the music. That's SomaFM, not a bug.
- Amapiano, classical and some other genres have no SomaFM station and route to radio-browser, where quality varies more.
./tests/run.sh # everything; prints the total
./tests/run.sh unit # offline, deterministic
./tests/run.sh integration # real mpv with --ao=null, no audio device needed
./tests/run.sh contract # live upstreams; nightly in CI, never on PRs
Run it under dash before pushing. macOS /bin/sh is bash in POSIX mode and
is far more forgiving than the dash on the Linux CI leg:
SELECTA_SH=/bin/dash TERM=dumb ./tests/run.sh
integration/playback.sh runs real mpv against an av://lavfi tone and
asserts the whole chain: supervisor start, state, segment, launcher render,
volume, pause, stop, teardown. No network, no audio device, deterministic.
The status line has its own adversarial suite: missing, empty, truncated,
corrupt and directory-shaped state, 4-byte UTF-8, glob characters and tabs in
track titles, and a non-numeric $COLUMNS. Every case must exit 0.
evals/ holds behavioural cases for the skills, led by the three
no-autostart* ones: music must never start unless you asked for it. They are
graded by reading, not by running — there is no eval runner here, and
tests/run.sh does not execute them. See evals/README.md.
The repo root is the marketplace; each plugin lives under plugins/. tests/,
evals/ and demo/ sit at the root and are not part of either install payload.
claude plugin install from a directory source copies the plugin into
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/. It does not
reference your working tree, and the staleness is invisible: the command works,
it's just running old code.
While developing, run the repo directly:
./plugins/selecta/bin/selecta play ambient
./tests/run.shclaude plugin update compares versions, so with the version unchanged it is
a no-op: it reports success and /selecta keeps running the code from whenever
you last bumped. To actually refresh an installed copy:
tools/reinstall.sh # both plugins
tools/reinstall.sh selecta # just the oneThis only works while the marketplace points at your working tree. Adding it from GitHub replaces that silently, and then a reinstall gives you the published code while looking like it gave you your edits, so the script checks and refuses rather than misleading you:
claude plugin marketplace remove azandabot-selecta
claude plugin marketplace add /path/to/your/cloneIt validates the manifests first, deletes the cached copy, reinstalls, and
diffs the result against the tree so a silent partial install cannot pass.
Restart the session afterwards: skills, commands and hooks are read at session
start. The selecta binaries work immediately either way.
selecta doctor prints the directory it's running from, so you can always tell
which copy answered.
Issues and pull requests welcome. ./tests/run.sh and shellcheck -x -s sh
should both be clean, and claude plugin validate --strict should pass for both
plugins and the marketplace.
If selecta earned a place in your status line, a ⭐ helps other people find it.
MIT
