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 length-delimited JSON. Unix builds use Unix-domain sockets. Windows builds use Tokio named pipes. 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, the macOS app) 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

The 2026-05-28 latency lesson

The slow-TUI investigation turned into a reusable daemon lesson: the daemon should keep useful music state warm even when no TUI is open, but that work must not compete with playback controls.

The 2026-05-28 device lesson

The "current daemon device is missing, TUI play chooses some other device" bug was a device-registry and fallback-policy bug, not a TUI bug.

Validated against code:

Reusable notes from this:

The 2026-05-29 auth prompt lesson

The keychain prompt storm was not just an auth bug. It was a side-effect bug.

Validated against code:

Reusable note from this:

The 2026-06-02 startup seed lesson

The "rate limited on the first song after opening the app" bug was not really a
playback bug. Opening the TUI was spending Spotify Web API budget before the
user pressed play.

Validated against code:

The reusable lesson is the same Hot Path vs Warm Path rule, but sharper:
client startup is not automatically a reason to call the provider. Seed the
client from reusable daemon truth, then let explicit actions and daemon-owned
warm loops refresh the world.

The 2026-06-02 player hot-path lesson

Release v0.1.35 fixed the part that optimism alone could not fix: the TUI
could show "paused" quickly while the audio kept playing for a few seconds.
That felt wrong because the physical side effect was still waiting behind a
slower path.

Validated against code:

Reusable notes from this:

The 2026-06-03 playlist context lesson

The playlist bug was a queue-truth bug, not just a playback bug. Starting a
playlist from the TUI did make sound, but the daemon did not publish the
context queue, so Home, Queue, CLI watchers, MCP clients, and agents could
still see an empty queue.

Validated against code:

Reusable notes from this:

The 2026-06-09 analytics provenance lesson

The original audit found Last.fm import and sample-counted playback still
unfinished. Both have since shipped, but the rule from that audit still holds:
imported history can enrich local analytics, but it is not the same thing as
observed playback.

Current code truth, revalidated 2026-08-12:

Reusable notes from this:

The 2026-08-12 comparison docs lesson

The ncspot and spotify-player comparison pages answered a real question: why
build this instead of using an existing terminal player? The answer is lineage
plus a boundary, not a feature contest.

Validated against current source and releases:

Reusable notes from this:

The 2026-06-02 release-lockfile lesson

The Release Please failure on PR 18 was not an upstream release-please bug. The
repo used release-type = "simple", so Release Please correctly updated the
manifest, changelog, and workspace version in Cargo.toml. Cargo still refused
--locked CI because Cargo.lock kept the old workspace package versions.

Current code truth:

That belongs with Release PRs Must Own Derived Version State and Release Gates Encode Product Contracts. The release PR has to carry the whole version graph, not just the files a human reads first.

The 2026-06-09 V1 hardening lesson

The V1 shape is narrower than "Spotify app for everyone" and stronger because
of that. spotuify is BYO Spotify Developer app GA: users create their own
Spotify OAuth app, paste the client_id, and use PKCE. A fresh
first-party/keymaster setup opts in through SPOTUIFY_USE_FIRST_PARTY=1 because
keymaster-backed Web API polling gets a worse rate-limit posture.

Validated against code and docs:

The reusable lesson is mostly Release Artifacts Are Product Promises:
publishing a Windows zip changes the product promise even if Windows remains
beta. Publishing a DMG changes a different promise again. The docs have to name
which surface they mean.

The 2026-06-09 daemon readiness lesson

The false "daemon did not become stable within 5s" failure was not a crash. It
was a readiness-budget mismatch.

Validated against current code:

The immediate fix is valid: do not declare failure before a legitimate startup
path has had time to finish. The deeper design note is [[Daemon Readiness Is Not
Process Liveness]]: if the daemon has slow pre-bind work, either the launcher
wait has to cover it, or the daemon should bind a small status surface early and
report player readiness separately.

The 2026-09-07 native audio teardown lesson

The silent-playback incident crossed three layers: Spotify control state,
Spotuify's native sink, and macOS CoreAudio. The first two looked alive while the
third could not play a one-second system sound.

Validated against release v0.1.102, runtime evidence, and origin/main at
97cdbd5:

The reusable rule is A Teardown Timeout Does Not Reclaim the Resource. The
debugging rule is Put the Probe Where the Fault Runs: use a system audio
probe below the app, then a PCM counter inside the sink. Process existence,
session connectivity, and a successful command are all too high in the stack.

The documentation audit found two other current truths. Release v0.1.102 exposes
65 user commands, excluding help, and 54 MCP tools. The public README and site
still said 58/60 commands and 41 tools. The v0.1.102 macOS CLI jobs also passed
while explicitly skipping signing because the repository secrets were absent.
Workflow capability is not artifact state.

The 2026-05-23 idiomatic audit

A full idiomatic-Rust pass against Idiomatic Rust Rubric. At the time, the 14 existing crates had zero unwrap/expect/panic calls in non-test production code. The findings that became reusable notes:

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

See also