A privacy-first Spotify player TUI for the terminal, with embedded streaming and a tmux now-playing integration. It plays audio itself through librespot, so you do not need the Spotify desktop app running.
- Embedded playback via librespot
- Search, playlists, albums, and library discovery for top tracks/artists, recently played, and saved tracks
- Privacy-first auth with PKCE, no client secret, Keychain-only refresh tokens, and no telemetry
- tmux status bar support through a fast
now-playingsubcommand
Requires macOS and a Spotify Premium account. librespot cannot stream on free Spotify accounts.
With Homebrew, after the first public release is published:
brew tap blacktop/tap
brew install --cask spot-defy
spot-defy auth login
spot-defyFrom source:
cargo build --release --locked
./target/release/spot-defy auth login
./target/release/spot-defyRunning spot-defy without logging in first also works; it opens the browser
login flow automatically.
- macOS on Apple Silicon or Intel
- A Spotify Premium account
- Rust 1.85 or newer when building from source
- Optional: tmux for the status-bar integration
Spotify rate-limits Web API calls made with librespot's streaming client id. spot-defy follows the same approach as spotify-player and ncspot: one PKCE flow uses the librespot client id only for streaming, and a separate Web API client id handles search, playlists, albums, and library reads. Both are one-time browser authorizations; the rotating refresh tokens are cached in the macOS Keychain.
To use your own Spotify Developer app for Web API quota, create an app at the
Spotify Developer Dashboard, add
http://127.0.0.1:8899/login as a redirect URI, and set the app id in
~/Library/Application Support/spot-defy/config.toml:
client_id = "your-app-client-id"
# redirect_port = 8899
[keybindings]
# Single-character normal-mode bindings.
quit = "q"
down = "j"
up = "k"
play_pause = " "
next = "n"
previous = "p"
search = "/"
[theme]
# Supported names include standard ratatui colors plus spotify-green,
# spotify-black, info-cyan, and info-cyan-dim.
accent = "spotify-green"
progress = "info-cyan"
dim = "gray"Screens & Global
| Key | Action |
|---|---|
1 / 2 / 3 |
Search / Playlists / Library |
/ |
Start a search |
Tab / Shift-Tab |
Cycle Library tabs or Search lanes |
q / Ctrl-C |
Quit |
Navigation
| Key | Action |
|---|---|
j / ↓, k / ↑ |
Move down / up |
g / Home, G / End |
Jump to first / last |
Enter |
Play a track or open an album/artist/playlist |
Esc |
Back |
Playback
| Key | Action |
|---|---|
Space |
Play / pause |
n / p |
Next / previous track |
→ / l, ← / h |
Seek +5s / -5s |
+ / =, - / _ |
Volume +5 / -5 |
Search Input
| Key | Action |
|---|---|
| type | Search automatically after a short pause |
Enter |
Search immediately |
Esc |
Stop editing |
With the TUI running, show the current track in your tmux status bar:
set -g status-interval 5
set -g status-right-length 100
set -g status-right "#(/path/to/spot-defy now-playing)"tmux runs #(...) commands with its own PATH, so use the absolute binary path
from:
command -v spot-defyspot-defy now-playing reads the running app's Unix socket and prints one line
with a hard 400 ms timeout. When nothing is playing, auth is missing, or the TUI
is not running, it prints an empty line and exits successfully. Full details are
in docs/tmux.md.
| Command | Description |
|---|---|
spot-defy |
Launch the interactive TUI |
spot-defy auth login |
Run the streaming and Web API OAuth login flows |
spot-defy auth logout |
Remove stored refresh tokens from the Keychain |
spot-defy now-playing |
Print a one-line now-playing status for tmux |
- PKCE OAuth: no Spotify client secret is stored anywhere.
- Keychain-only persistence: only rotating refresh tokens are persisted, in
the macOS Keychain service
spot-defy. - Memory-only access tokens: access tokens stay in memory and are never written to disk.
- Log filtering: known token-bearing
TRACEtargets from librespot OAuth code are dropped before logs reach disk. - No telemetry: the app talks only to Spotify.
Run spot-defy auth logout to remove stored refresh tokens.
Maintainer release flow:
just check
just bump
# wait for the GitHub Actions release to finish
just tap-update (just version)just bump uses svu patch to pick the next version from Git tags, updates
Cargo.toml and Cargo.lock, commits the release bump, pushes main, and
pushes the matching annotated vX.Y.Z tag. Use just bump-minor or
just bump-major for larger version increments.
GitHub Actions builds Apple Silicon and Intel macOS release tarballs, publishes
them to the GitHub release, and uploads checksums.txt.
just tap-update downloads checksums.txt, generates
../homebrew-tap/Casks/spot-defy.rb, and commits the tap change locally. Use
just tap-update-push 0.1.0 when you want the tap commit pushed as part of the
same step.
- First source build is slow. librespot pulls a large dependency tree; subsequent builds are much faster.
- Track unavailable. Spotify Premium is required for librespot streaming.
- Login problems or stale token. Run
spot-defy auth logout, thenspot-defy auth login. - tmux shows nothing. The full TUI must be running; it serves the now-playing socket.
MIT