Repository navigation
spotify-cli v0.1.3
Playback control:
likeandunlikenever change the wrong song. spotify_player can only like the track it
believes is playing, which could be an earlier one, so they now check that it is the song
Spotify.app plays. If it is not, they nudge it once (an inaudibleplayback volume <current>)
and wait up toverify_timeout_ms+ 1.1 s. If it still does not match, they refuse with the new
track_mismatchand change nothing (before,likecould save the previous song, or report
success for a podcast episode and do nothing). Episodes, ads and local files are refused as
unsupported.- Before a spotify_player command whose effect depends on its own memory (play, pause, toggle,
shuffle, next, previous, seek), that memory is compared with Spotify.app. When they disagree,
AppleScript acts at once andfallback.reason.codeis the newstate_mismatch; under strategy
spotify_playerthat is the error.togglesends an explicit pause or play chosen from
Spotify.app's state. - Seeks are exact: AppleScript sets the position first; spotify_player's relative seek is only
the fallback, and under strategyspotify_playerit is refused withstate_mismatchwhen
spotify_player is on another item.play <track|episode|show>goes to AppleScript only, so a
start never leaves Spotify.app stopped with nothing loaded. play --likedplays the Liked Songs list itself through AppleScript, sonextandprevious
stay in it; it no longer stops playback.--randomturns shuffle on for Liked Songs (Spotify
keeps it) and starts at a random song;--limitapplies only when spotify_player starts the
list. A failed start that leaves Spotify.app empty puts back what was playing, under any
strategy (details.restored, ordetails.restore_failed).- Refusals the running spotify_player logs (429, 403) are noticed at once instead of after the
verify timeout:fallback.reasonisrate_limited,no_effectorno_active_device, with
details.requestanddetails.spotify_player.play,pause,toggle,volumeand
shufflewait at most 1.5 s before falling back. With the state checks and AppleScript-first
seeks,toggle,seekandpreviousno longer take 3.6–6.8 s when spotify_player's command
would have no effect. previousbehaves the same on every path: from 3 s in, or when there is no item before it,
it restarts the item; otherwise it goes to the previous item. The outcome's newresult
(restartedorprevious_item) and the first line of the human output ("Back to the start of
this item." / "Back to the previous item.") say which.repeatfollows spotify_player's real order (off → track → context), confirms each step and
checks the result in Spotify.app. It no longer reports success while leaving another mode, and
arepeat offit cannot confirm never passes through repeat-one. When a refused step leaves
repeat-one on, it returns that failure instead of an AppleScript success that leaves the song
repeating. When spotify_player refuses for now,repeat trackreturns its error (e.g.
rate_limited, retryable) instead ofunsupported.- AppleScript volume lands on the requested level (it was one below for most levels); 19, 39,
59, 79 and 99 land one above. Verification is exact. spotify status --full:webgainsitem_uri,is_playing,source(spotify_playeror
web_api) andstale. When spotify_player's memory disagrees with Spotify.app, a fresh Web API
read (1–4 s) is used instead; when even that is for another item,web.staleis true, repeat
and shuffle that contradict Spotify.app are left out, and the new warningweb_state_stale
says so. New warnings also includeno_active_deviceandtimeout. The human output then
takes repeat from Spotify.app and adds "web data out of date".- Time left,
(-m:ss), inspotify statusandspotify trackis rounded to the nearest
second, as isplayback.remainingin trigger firings: a--remaining 20sfiring reads 0:20,
not 0:19 (remaining_msis unchanged). spotify launch --jsonanswerslaunched: false, already_running: truewhen Spotify.app was
already running (it saidlaunched: true) and does not runopen; the human output says
"Spotify.app was already running." or "Started Spotify.app (hidden).".
Queue:
- Quick or concurrent
spotify nexts are applied one after another and never hand off the same
item or lose one. Anextthat arrives while the previous hand-off is still in flight skips
that item and plays the one after it (skippedin the reply, and a
skipped <uri> (it was still being switched to)line). play <item>(also--likedand radios) andpreviouswait for anextor queue hand-off in
progress, and those wait for them, so changes reach Spotify in the order they were asked. A
failedplay <item>keeps the queue's resume point; if the managed item still plays, it stays
the managed item.- Episodes queued by URI get their name, show and length (a Web API lookup with
spotify_player's cached token), andqueue add --searchpasses the chosen hit's facts to the
daemon (known). Items without a known name show asepisode <id>/track <id>, Spotify's
upcoming items show their artists or show, andqueue addprints what it added
("Queued 1 item. Managed queue now has 3." plus a+ name — byline per item). Albums,
artists, playlists and shows are refused as "is an album" (and so on), with a hint that fits. spotify queueleaves out Spotify's repeats of the item playing now (an episode played without
a context was listed ten times) and prints a note saying so (current_repeats_left_out,
notein--json).- Resuming the interrupted list after the managed queue drains also works when its next item is
a podcast episode (its URI was built as a track's).
Details, library and playlists:
spotify trackshowsliked: yes|nofor songs (likedin--json, from a Web API check made
alongside the lookup, left out when it fails or takes over 2.5 s).spotify track <playlist> --jsonhas the same envelope as the other kinds:kind,item,
playlist,owner,collaborative,track_count,duration_ms,duration,tracks,raw.spotify track --typeoffers onlytrack,album,artistandplaylist;showor
episodeis a usage error. A bare id that is not found as a track gets a hint to pass
--type album|artist|playlistor a full URI.- Library and playlist reads (
library <section>,playlist list,playlist show,
track <uri>) are retried once by the daemon after a transienttransportor
spotify_player_busyerror, andlibrary,playlist listandplaylist showonce more by the
CLI. Rate limits, timeouts and writes are not retried. - A library or playlist read right after a playlist change or like/unlike waits until it can see
the change (1.1 s), also when another identical read was in flight during the change, so
playlist listright afterplaylist deleteno longer lists the deleted playlist.
Daemon and its spotify_player:
- The warm spotify_player is the daemon's own and actually serves the CLI: copies left by earlier
daemons (the pid in the newwarm-player.json, or orphans with the daemon's exact overrides)
are stopped before a start; it is reportedrunningonly once it holds the client port;
another spotify_player holding the port (your own TUI) is left alone and reported as
deferredwith its pid; a copy that never gets the port is stopped and retried
(not_serving). It dies with the daemon, even on SIGKILL. - The warm copy refreshes playback every 3 s (
playback_refresh_duration_in_ms; a smaller
positive value in yourapp.tomlis kept), about 20 small Web API requests a minute, so its
view is rarely more than 3 s behind Spotify.app. spotify daemon statusdescribes the warm spotify_player in words (pid and port, refresh
interval, who holds the port, retry delays, errors).spotify doctor's
spotify_player_warm_instancecheck passes only when the daemon's own copy holds the port, and
shows the state and the pid that holds the port when it fails.spotify daemon restartwith the launchd agent loaded restarts the job in place
(launchctl kickstart -k) and waits for a new pid; it used to stop the daemon and never start
it again. It prints "spotify-daemon restarted via launchd (pid N, vX).".daemon stopand
uninstallwait until launchd has unloaded the job;daemon startwaits up to 15 s and keeps
retrying while a previous instance is still exiting.spotify update --checkreports the realauto_updatesetting and no longer leaves
daemon statussaying it is off.- The telemetry relay drains the whole backlog (up to 2 000 events) every minute instead of 40
events a minute.
CLI:
- Human output keeps Spotify names on one line (line breaks, tabs and other control characters
become spaces);--jsonkeeps the original strings. While a podcast episode plays, the status
header has no dangling " — ", andspotify trackshowsshow:instead ofalbum:. - The
spotify searchfooter suggestsqueue it: spotify queue add <uri>only for tracks and
episodes, andlook inside: spotify track <uri>for albums, artists and playlists. spotify report --attachtakes text files only: binary files, directories and a home's
session.jsonortesting.jsonare refused withinvalid_inputbefore anything is sent. Of
long files only the last 64 KiB are sent, starting at a whole character.- Every
usageerror hasdetails.usage, anddetails.argumentanddetails.valuewhen the
parser names them; the hint always includes the usage line.--limit -1is
invalid_input("--limit -1 is out of range: it takes 1 to 10.") instead of the misleading
-- -1tip, and a hyphenated value after an option (--note -x) gets the tip to write
--note=-x. spotify config set search_limit=5orconfig set search_limit 5gets the JSON form as its
hint:spotify config set '{"search_limit": 5}'.config set --helplists examples.spotify doctor,daemon status,queue,launch,previousandnextsay more in human
output, as described above; the--helpofstatus,play,toggle,previous,seek,
repeat,like,unlike,launchandtrackdescribes the new behaviour.
Docs:
- The playback guide documents the control rule and its exceptions, the state checks, refusals,
previous,repeat, volume, like/unlike and failed-start restores; the daemon guide the warm
spotify_player, its states and costs, restart and library reads; the queue guidenextunder
concurrency, ordering with explicit commands and item names. The error table gains
state_mismatch,track_mismatchandweb_state_staleand dropsqueue_empty, which no
command returns (spotify nextwith an empty managed queue runs Spotify's own next). - Developers can run control commands against Spotify.app without the daemon:
cargo run -p silicon-spotify-client --example control -- <command>.