Spotuify
Spotuify
A daemon-backed, CLI-first, keyboard-native Spotify controller and music library runtime for the terminal. Same architectural blueprint as Mxr, pointed at music instead of email.
Project lives at: ~/code/bhekanik/spotuify/
Repository: https://github.com/planetaryescape/spotuify
In one paragraph
A single binary spotuify with subcommands. Bare spotuify opens a TUI; spotuify search, spotuify play, spotuify queue add, etc. are CLI subcommands that talk to a local daemon over a Unix socket with length-delimited JSON. The daemon owns runtime state (current playback, queue, devices), SQLite (cached metadata, canonical local truth), Tantivy (search, rebuildable), and playback via embedded librespot driving a Spotify Connect device. Everything else (TUI, CLI, MCP, agents) is a client of the same IPC contract. The CLI is the canonical surface, which is also why an agent can drive and verify every feature end to end.
The rules it shares with mxr
- Daemon owns state; clients are views. Even optimistic UI updates originate from the daemon as events, never flipped locally in the TUI. See Client-Agnostic Cores.
- CLI-first. Every TUI/MCP capability has an equivalent CLI surface in the same change. A feature that only lives in the TUI is incomplete.
- Mutations are dry-run-first through the same selection path as the real run. See Same-Code-Path Preview and Mutations Documented Dry-Run First.
- The crate dependency graph is the architecture, enforced by Cargo (
tests/workspace_boundaries.rs), not convention. 14 crates:coredepends on nothing internal,protocol/store/searchdepend only oncore, clients can't reach pastprotocol. - Every external operation has a bounded timeout (keychain, Spotify Web API, librespot, IPC, image fetch). Non-negotiable. See Tokio.
The 2026-05-23 idiomatic audit
A full idiomatic-Rust pass against Idiomatic Rust Rubric. Verdict: already strong, production-minded Rust — zero unwrap/expect/panic in non-test production code across all 14 crates. The findings that became reusable notes:
- Idiomatic Is Not Pedantic-Clean — the 2014-warning pedantic sweep was ~90% noise; the rubric's job is to say what to ignore.
- Carry Error Kinds Don't Re-derive Them — the
classify_error_kindsubstring-matching anti-pattern lived here. - Use VecDeque for Bounded FIFOs — the daemon event log's
Vec+remove(0)FIFO, fixed. - Refactor Behind a Behavior Test — the discipline used for every change, and the reason the keychain-bound auth fix was deferred rather than done blind.
Weak cells were structural (a 1190-line daemon dispatch function, a 79-field TUI App) and a few async-blocking spots, never safety or correctness. The high-risk rewrites were documented and deferred.
Gotchas worth remembering
- Dev builds default to instance
spotuify-dev; installed builds tospotuify. Unsigned dev binaries reading the macOS keychain trigger a prompt storm that can jamsecurityd. - A full
cargo test --workspacehangs in an agent environment becausecli_exit_codeshits the real keychain (no fake provider). Verify the daemon path withcli_fake_daemon(fake provider, isolated socket) instead. - Docs ship to
spotuify.vercel.appvia Vercel; the project's Root Directory must besiteornpm cifails on the repo root.
See also
- Mxr — the architecture this copies; the reuse map is explicit in spotuify's docs
- Idiomatic Rust Rubric — the standard the audit ran against
- Tokio — the async hub spotuify's runtime rules come from