Skip to content

v0.41.0

Latest

Choose a tag to compare

@github-actions github-actions released this 10 Aug 20:56
· 30 commits to main since this release

Added

  • MCP server: drive spotatui from your coding agent, with no API key (--features mcp-server): spotatui can expose your player and listening history as Model Context Protocol tools, so Claude Code, Codex, Gemini CLI, or any MCP client can act as your DJ — reading what you have been listening to, searching the catalogue, and queueing tracks. It rides the agent subscription you already have, so there is no key to configure and no LLM client compiled into spotatui. Setup is one line handed to your agent: point it at docs/mcp-setup.md, which is written as instructions for an agent to follow, and spotatui mcp status gives it a safe, machine-checkable probe of each step (never run spotatui mcp to test — that command is the server and blocks on stdin). Off by default: set behavior.mcp_enabled: true, and the socket binds loopback-only behind a token in ~/.config/spotatui/mcp.json. Written against protocol revision 2026-07-28 — which removed the initialize handshake and made MCP stateless — and dual-era, so it also answers initialize for the clients shipping today, which would otherwise fail outright against a modern-only server. Ask an agent for "songs like X" and a good recommender names tracks you already own, so search_tracks marks every result [owned] or [new] — Liked Songs plus playlists you own or collaborate on — which reaches the agent exactly when it is choosing, with no extra round trip. queue_tracks also takes exclude_owned: true for an agent that wants the guarantee rather than the hint; it reports what it skipped and substitutes nothing. Neither is on by default: an agent told to queue one specific track you happen to own should get that track, not an explanation.

  • Agent plugin: connect your agent to the MCP server, with a DJ skill, in one install: agent-plugin/ packages the MCP server registration together with a spotatui-dj skill that teaches the agent how to DJ — read the listening history first, search before queueing by name, queue rather than interrupt. It ships in both the vendor-neutral Agent Plugins 1.0 format and Claude Code's plugin format from the same directory, and installs from this repo's marketplace (/plugin marketplace add LargeModGames/spotatui, then /plugin install spotatui@spotatui). It replaces the client-registration step of docs/mcp-setup.md only; the binary still needs --features mcp-server and behavior.mcp_enabled: true. These are agent plugins for AI clients, unrelated to spotatui's in-app Lua plugins.

  • AI DJ inside spotatui (--features ai-dj): a DJ screen (Ctrl + J) with a chat prompt, continuous auto-queue (Ctrl + T), an "only tracks I don't already have" filter (Ctrl + O), and a vibe shift (Ctrl + Y) that drops the DJ's own queued tracks so a change of direction is audible now rather than six tracks from now. It holds a conversation and drives the same tools an MCP client does: a turn is up to four steps, and each step the model can search the catalogue, read your queue, queue tracks, or simply reply — so it can ask what you actually want before it plays anything, and answer a question without queueing at all. The tool list is the MCP server's own table rather than a copy, so the two front doors can never drift apart in what they can do. Every tool call is shown in the transcript as it runs, and the …thinking (2/4) row keeps a slow turn distinguishable from a hang, which matters because each step of an agent-CLI turn is a fresh subprocess. The auto-queue refill and the vibe shift deliberately get two steps and may not stop to ask anything: nobody is watching the screen for either. The brain is pluggable: a locally installed agent CLI (claude, codex, agy for Antigravity, copilot for GitHub Copilot, opencode, or anything headless, with no API key), the Anthropic Messages API, or any OpenAI-compatible endpoint including local models via Ollama and LM Studio. The first time you open it, the DJ asks which of your installed agents to use and which of that agent's models, because the default backend spends the coding subscription you already pay for and the heaviest model exhausts a Claude Pro plan in a handful of turns; the answer reaches an agent CLI as that CLI's own flag (claude --model haiku, agy --model gemini-3.6-flash-low) and the API backends as a model id priced per token, and Ctrl + G reopens the picker at any time. Only aggregate names from your local listening history ever reach a model (no identifiers, no timestamps), and suggestions the catalogue cannot confidently match are dropped rather than approximated, so an invented track never becomes a near-miss in your queue. This is spotatui's own DJ rather than Spotify's, which cannot be started through the public API (#196); it also sidesteps Spotify's restriction of /recommendations to apps registered before 2024-11-27 by having the model do the recommending. See docs/ai-dj.md.

  • Help search highlights its matches: While filtering the Help menu (search key, / by default), every occurrence of your search terms is now highlighted in the visible rows, so you can see at a glance which part of a row matched. Highlighting follows the same smart-case rule as the filter itself (#408).

  • Generated app state now uses XDG state/cache directories: config.yml stays in the app config directory for user-authored settings, while runtime-managed state (state.yml), listening history, last playback session, and Spotify token caches move to the app state directory. Native streaming credentials and audio cache move to the app cache directory. Existing config-dir runtime fields, radio favorites, listening history, playback-session files, Spotify token caches, and legacy native streaming credentials and audio cache are migrated on first use when the new target path does not already exist, preserving free-source startup, existing in-app radio favorites, Spotify login sessions, and native streaming setup during upgrade. If a new state/cache target already exists, the legacy file or directory is left in place instead of being merged or overwritten.

  • Community playlist pinned in the sidebar: A shared, Discord-curated Spotify playlist now sits pinned at the top of your Spotify playlists, so you can open it in one keystroke without following it first. It is toggleable from Settings → Behavior (behavior.pin_community_playlist, default on), and a one-time prompt on first launch explains that you add songs to it with the /queue command in the spotatui Discord and lets you hide the pin. The pin disappears automatically once you follow the playlist yourself, so it never shows twice. The + Add Playlist button also moved to the top of the sidebar and is now clickable with the mouse.

Changed

  • The Logging to: … startup notice now goes to stderr instead of stdout, so piping a subcommand's output (spotatui history recap > file) no longer captures it. stdout is program output; this matters most for spotatui mcp, where the MCP spec requires that nothing but protocol messages reach stdout.

Fixed

  • Lyrics fall back to a fuzzy search when the exact lookup misses: spotatui asked LRCLIB only through /api/get, which is an exact signature match: title, artist, and duration (in whole seconds) all have to agree with LRCLIB's record. A small metadata difference, a duration off by a second or a slightly different title spelling, returned a 404, and the track showed "No lyrics for this track" even when LRCLIB clearly had it. A miss now falls back to the fuzzy /api/search endpoint, and among its hits spotatui prefers timestamped lyrics over plain ones, then the result whose duration is closest to the playing track so the synced timestamps line up (#410).
  • Lyrics now load for collaborations: A track credited to more than one artist (e.g. "Take Me Back" by Kygo and Max McNown) showed "No lyrics for this track" even when LRCLIB clearly had it. LRCLIB indexes each track under a single artist string, almost always the primary one, but spotatui sent the full joined credit ("Kygo, Max McNown") to both the exact /api/get and the fuzzy /api/search endpoint, so every collaboration missed. The playback snapshot now carries the artist names as a structured list instead of one pre-joined string (which also makes MPRIS xesam:artist a real array), and the LRCLIB lookup falls back to the primary artist alone when the full credit finds nothing (#410).
  • Pausing a natively queued Spotify track actually pauses it: With spotatui as the playback device, pressing play/pause on a track played through the native queue (or from a raw track list like Liked Songs) flipped the playbar to paused while the audio kept playing, and resume was broken the same way. Those tracks are loaded directly into the player rather than through a Spotify Connect context, and the pause/resume commands were routed only to the Connect layer, which silently ignores them for tracks it did not start itself. Play and pause now also drive the player directly, so they always act on whatever is audibly playing. This same routing gap made several internal safeguards silent no-ops (the guard that stops stray Spotify audio from playing over a queued track, and the preemptive pause during queue handoff at end of track), so ghost audio and occasional stuck auto-advances around the native queue should be resolved as well.
  • Transient native 403 "Restriction violated" no longer throws the full-screen error: With spotatui as the playback device, an intermittent Spotify 403 "Player command failed: Restriction violated" could take over the screen during ordinary playback while the native Connect session was still settling, most visibly when the shuffle command that follows a successful play was rejected even though the music had already started. A rejected post-start shuffle toggle is now a status message instead of the Error screen (and shuffle state is no longer recorded as applied when the toggle was refused), and a transient restriction violation on other player commands de-escalates to a status message while native activation or recovery is pending. A genuine, steady-state restriction still surfaces (#424).
  • Streaming login survives a browser's stray connection: The native-streaming consent hands callback port 8989 to librespot, whose callback server accepted exactly one connection and gave up if it was not the redirect. Some browsers, LibreWolf in particular, open a connection without writing to it (or send a bare CRLF) before the real callback arrives. librespot consumed that, failed to parse it, and dropped the listener, so the redirect carrying the login code hit a closed port: the browser showed "unable to connect", and because the flow errored before credentials were saved, it repeated on every launch, which is the "streaming cookie does not get cached" several reporters described. spotatui's own Web API callback server never had this problem, which is why Web API login worked on the same machine where streaming login did not. Fixed in the pinned librespot fork: the accept loop keeps listening across unrelated connections under an overall deadline, and a captured code is no longer discarded when the success page fails to write. The port probe that precedes the login is now advisory rather than fatal as well: it works by binding and releasing, so the port is unowned between its release and librespot's own bind and the probe can never be authoritative. A busy port is logged and the login goes ahead, leaving librespot's bind as the real answer, instead of refusing an attempt that would probably have succeeded (#414; the same root cause behind #234 and #364, and upstream librespot#1705).
  • Startup no longer deadlocks when an update installs during login: The auto-update check ran concurrently with authentication and re-executed the new binary the moment an install succeeded. That re-exec blocks its own task, so the authentication future it was joined with stopped being polled while still holding the OAuth callback port, and the child process, which repeats startup from scratch, could not bind that port. The parent waited on the child, the child waited on a port the parent would never release, and both fought over the same terminal. The update check still overlaps authentication, since that is a network round trip worth overlapping; only the restart moved, to after both have finished. Authentication persists its token before returning, so the child reuses it instead of opening a second browser login, and the restart still runs ahead of a propagated auth error so a broken auth state can restart into the newer build that may fix it.
  • Log files land somewhere you can open on Windows: The log path was hard-coded to /tmp/spotatui_logs/, and the help screen and docs repeated that literal string. On Windows that is a drive-relative path the user's own shell cannot resolve, printed directly above the line inviting them to report a bug, so anyone asked for a log file had to guess where it went. The path now resolves through std::env::temp_dir(), so Windows lands in %TEMP% and POSIX platforms keep TMPDIR or /tmp, with a single helper owning it so the location shown is always the location written. Logs stay in the temp directory rather than moving to the state directory, because a file is written per process id and temp is the one place the platform clears on its own; the directory is now created with 0700 permissions instead of the default mode, since it sits in a shared location under a predictable name where any other local user could read it.

Downloads

  • On Windows 10/11 (64-bit)spotatui-windows-x86_64.zip
  • On Linux (Ubuntu, Arch, Fedora, etc.)spotatui-linux-x86_64.tar.gz
  • On Linux ARM64 (Raspberry Pi 4/5, ARM servers)spotatui-linux-aarch64.tar.gz
  • On macOS with Intel CPUspotatui-macos-x86_64.tar.gz
  • On macOS with Apple Silicon (M1/M2/M3)spotatui-macos-aarch64.tar.gz

Or just run the one-line installer: curl -fsSL https://spotatui.com/install.sh | bash (macOS/Linux) or irm https://spotatui.com/install.ps1 | iex (Windows).

Checksums (.sha256) are optional and only needed if you want to verify the download.

The Linux and Windows builds include the extra music sources (Local Files, Subsonic, Internet Radio, YouTube). The YouTube source needs yt-dlp installed (ffmpeg recommended).

To update use "spotatui update --install" or use your package manager of choice.