Repository navigation
spotify-cli v0.1.6
Playback control:
- Every start goes through the Spotify Web API first, so Spotify.app stays in the background:
songs, episodes, shows and Liked Songs directly (new), albums, playlists, artists and radios
through spotify_player (as before). Starts fall back to AppleScript, except a radio, which has
no AppleScript way; only exact seeks go to AppleScript first. The outcome says
via: "web_api"(new) once Spotify.app plays the item. Before, songs, episodes, shows and
Liked Songs started through AppleScript, which brought Spotify.app to the front. spotify play <track|episode>(alsoplay --search,podcast playandsearch --playwhen
they pick a song or episode, and a song with--context) plays in--contextwhen given, else
in the song's album or the episode's show (looked up withGET /v1/tracks/{id}or
/v1/episodes/{id}in your market), from the start. The rest of the album then plays on, and
next/previousmove through it. A relinked song starts from the release that plays in your
market. A start that lands on another album track, or whose uri the album refuses, is sent
again once by the track's position (counted across discs). Lookups are remembered for the life
of the daemon (up to 500 items).spotify play spotify:show:<id>starts the show as its own list.spotify play --likedstarts the Liked Songs list itself at its first song, or with--random
at a random position below its size (GET /v1/me/tracks?limit=1). Once it plays, its shuffle
is set (PUT /v1/me/player/shuffle, or AppleScript'sset shufflingwhen that does not show),
and in order it starts again at the first song when the list began elsewhere. An empty Liked
Songs isnot_foundat once, and nothing starts. AppleScript remains the fallback, with the
focus handed back.spotify play --liked --shuffledoes the same as--liked --random.- Where a start plays: the devices are listed at every start (
GET /v1/me/player/devices). When
another device is active (a speaker or phone Spotify.app controls; never spotify_player's), the
start plays there instead of moving playback to this Mac, and the outcome's newnotesays so.
When Spotify.app plays on a device the Web API does not list, the start names no device.
Otherwise it goes to Spotify.app on this Mac: the Computer device named like this Mac
(scutil --get ComputerName), or, only when that name is unknown, the only Computer device;
another Mac and the Web Player are never picked. - A start answered with a server error (5xx), or not answered in time, is looked for in
Spotify.app before any fallback. When it plays, it counts (via: web_api, with anote) and
is not started a second time. - When the Web API start cannot run or does not take effect within
verify_timeout_ms,
AppleScript starts the item, withfallback: {"from": "web_api", "reason": …}:
rate_limited(a 429, or the pause after one),premium_required,device_restricted(new:
the active device takes no Web API commands),device_not_found(new),no_active_device,
unsupported(not playable in your market; nothing is sent),
spotify_auth_required/spotify_player_missing,web_api_failed(new),not_found,
transportorno_effect. When AppleScript fails too, the Web API attempt is in
details.first_attempt. The AppleScript fallback plays a song in the album the lookup found
(from the release that plays in your market), so the album goes on after it, unless
--contextwas given or the album did not place the song. Strategyapplescriptnever uses
the Web API; under strategyspotify_player, episodes, shows and songs with--contextcan
now start (through the Web API). - When an AppleScript start brings Spotify.app to the front (a fallback, an album or playlist
spotify_player could not start, the restore after a failed start, and the managed queue's
hand-offs and resumes), the focus goes back to the app that had it, and Spotify.app is hidden
again if it was hidden. The front is watched during the start and for 1 s after it;
lsappinfo setfrontis tried, thenopen -afor regular apps that still run (never Finder).
No new macOS permission is needed. The outcome carries
refocused: {app, via: "setfront"|"open", hid_spotify?, error?}(details.refocusedon
errors);focus_not_returned(new) appears only inside it. Measured: Spotify.app in front for
about 0.5 s instead of staying there. - The Web API rate-limit pause (
Retry-After, 1 s to 10 minutes, 30 s without one) is one
process-wide pause, shared by the daemon's lookups, searches and starts. - The
automation_permission_deniedhint starts with the exact place: System Settings →
Privacy & Security → Automation → spotify-daemon → turn on Spotify. play --likedwaits for Spotify to apply Liked Songs' saved shuffle setting (it switches a
moment after the first song starts) before setting the order you asked for, and verifies it:
plain--likedalways starts in order at the first liked song,--randomat a random song.- A song or episode Spotify will not play for this account (
details.reason:market,
explicitorproduct) fails at once with the newnot_playable(exit 1) and leaves playback
alone; before, the AppleScript attempt emptied Spotify.app for seconds first. - A relinked song starts in its album with one request, named by the id the album lists, so the
album's first song no longer plays for a moment.
Search and queue:
- When spotify_player's search fails (such as its "invalid type: null, expected a boolean" parse
error),search,podcast searchandplay --searchfall back to the Web API's own
GET /v1/search. Replies sayvia(spotify_playerorweb_api, new) and carry
fallback: {from: "spotify_player", reason}when the Web API answered; when both fail, the
error is the Web API's withdetails.first_attempt. spotify_player errors that end in a JSON
parse position (line 1 column 42912) are no longer mistaken for HTTP 429 or 401. spotify queue/queue listJSON has a new top-levelwarningsarray. It holds
no_active_devicewhen Spotify reports nothing playing on any device and Spotify.app does not
play either, so Spotify's upcoming list is empty for that reason. The human output prints each
warning asnote: <message> (<code>), with its hint on the next line.queue listhas the aliasesqueue showandqueue ls.
Lyrics:
spotify lyrics <uri|link|id>shows any track's lyrics, whether or not anything plays, and with
a target Spotify.app need not run;spotify lyricsalone is the song playing now. It takes a
track, not search words: to find a song by name, runspotify search '<words>' --type track,
thenspotify lyrics <uri>. One word that is no id (spotify lyrics 505) isinvalid_input
("505is not a track URI, link or id.") with a hint namingspotify search 505 --type track
anddetails.search; several words are a usage error whose hint names
spotify search '<words>' --type track.
Finding commands:
spotifywith no arguments prints an orientation instead of the help (exit 0, was 2): what is
set up and what is missing, each with its fix, what is playing, and up to five things to try
(such asspotify lyrics spotify:track:0BxE4FqsDD1Ot4YuBXwAPpwhen nothing plays). Local
checks only; the daemon is asked only when it already runs, never started.--jsongives
{version, ready, now_playing, checks, try, help}.spotify --helplists the commands grouped by goal (Listen, Find, Lyrics & details, Queue,
Playlists, Podcasts, Triggers (Ting), Account & login, Setup & diagnose), each with a summary
and an example. Most summaries were rewritten to say what the command can do, and every
command's--helpnow ends with Examples. A command's own flags are underOptions:, followed
byGlobal options:(--json,--org,--api-url,-h/--help,-V/--version); the root
help's heading isGlobal options (every command takes them):.-Von a subcommand prints
spotify <version>(wasspotify-queue …).- New
spotify how "<question>"[--limit 1-10] [--json]: answers offline with the best
commands and ready-to-run examples, the guide section to read and matching error codes. It
indexes the help, the guides, every setting and the error codes. A song or artist name in the
question never outweighs what you want to do, and it becomes made examples: lyrics get
spotify search '<name>' --type trackthenspotify lyrics <uri>, play, queue add and search
--search '<name>'; settings questions getspotify config set '{"<key>": <value>}',
completion questions both install lines, docs questionsspotify docs --search '<flag>'.
Questions that only ask to see something put read-only commands first. JSON:
{question, name, terms, commands, guides, errors, more}. An empty question is
invalid_input. Next:suggestions after human output, on stderr, with the real URIs and ids just printed
(search, podcast search, status, the play commands, track, lyrics, library, queue, queue add,
playlist list/show/create/fork, devices, trigger add/list, login, login status, doctor, config
set, how). Never with--json; the new environment variableSPOTIFY_HINTS=0turns them off.
They replace the search footer on stdout ("Play one: … · queue it: … · look inside: …"), and the
hints aftertrigger addandloginchanged to match.spotify commands --jsonkeeps every field and adds, per command,path,summary,
details,goal,goal_title,capabilities,keywords,subcommands,arg_groups,
examples,output,errors,requirements,requirement_notes,help,mutates(true
when it can change something or send something out),changes(fromplayback,library,
playlists,config,triggers,daemon,session) andread_only_when(e.g.volume
without a level); per argument,flag,value_name,takes_value,multiple,type,
allowed_values,default,min,max,conflicts_withandrequires; and at the top level
goals,errors(fromspotify docs errors),exit_codes,requirements,argument_types,
changes,mutates(how to read them) andoutput_contract.usagenow includes the full
path (Usage: spotify search …, wasUsage: search …).spotify commandsfor people is
grouped by goal. Read-only commands:
spotify commands --json | jq -r '.commands[] | select(.mutates == false).command'.- New
spotify completions <zsh|bash|fish|powershell>: the completion script;--helphas
install lines that work as written (zsh:~/.zfuncplus anfpath/compinitline in
~/.zshrc; bash: a~/.bash_completion.dfile sourced from~/.bashrc, also on macOS's bash
3.2; fish; PowerShell), and--jsongives{shell, script, install}. - New
spotify docs <topic> --section '<heading>': one section and its subsections (a whole
heading, then its start, then any part); an unknown one isnot_foundlisting the sections.
spotify docs <topic> --search <text>searches only that guide (the JSON gainstopic);
--searchtakes text that starts with a hyphen, cannot be combined with--section, and an
empty text isinvalid_input. - An unknown subcommand of a group exits 2 with a hint listing the real subcommands and their
aliases (queue nextadds "To skip to the next track: spotify next."; a top-level command's
name says it is a command of its own). iamis Silicon IAM's own CLI, separate from spotify-cli:spotify login --help, the root
help's Authentication lines andspotify iamnow say so and how to get it
(cargo install silicon-iam-cli), andspotify iam --jsongains
iam_cli: {name, description, install}.- The CLI no longer panics (exit 101) when the reader of its output goes away early (
| head). spotify trackshows the album with its URI (album: <name> (<uri>)); items gain
album_uri(tracks) or the show's URI (episodes) in--json.Next:hints print only when output goes to a terminal (never before piped content);
SPOTIFY_HINTS=alwayskeeps them,SPOTIFY_HINTS=0turns them off.- Typos suggest the closest command first (
spotify lyircs→lyrics). spotify login <word>with a short plain word (such asspotify login stauts) is rejected
locally, suggestingspotify login status, instead of being sent to the backend as an SLT.spotify auth login --helpandauth status --helphave examples and say what a headless
Silicon should do.
Configuration:
- New
keep_spotify_in_background(boolean, default true): give the focus back after an
AppleScript start brought Spotify.app forward.verify_timeout_msnow also bounds the wait for
a Web API start, and the look for a start whose answer was lost.
Library (silicon-spotify-client):
- New modules
webapi(theWebApitrait,HttpWebApi;startfor a track or episode,
start_listfor Liked Songs or a show,choose_deviceandtargetfor where a start goes,
set_shuffle,liked_songs,search,item_factsandcached_facts,unanswered, the
shared rate-limit pause andcached_access_tokens) andfocus(theFocustrait,
LaunchServices,keep_in_background).Via::WebApiis new,Outcomegainsrefocused,
noteandOutcome::new, andmodel::Item::from_web_jsonreads Web API search hits. - Breaking for code that builds a
Controller: it has two new fields,webandfocus. Pass
web: Err(…)andfocus: Noneto keep 0.1.5's behaviour (spotify docs development). - The
controlexample takes--no-web(refuses the Web API's playback commands; lookups still
answer),--no-refocusand--trace, and new commandsfront,handoff <uri> [context],
lookup <uri>,webstate,devices,websearch <query>andraw <GET|PUT> <path> ….
Daemon protocol:
player.playoutcomes may carryvia: "web_api",noteandrefocused;searchreplies
carryviaand, after a fallback,fallback;queue.listreplies carrywarnings.
Docs:
- New guide sections: "Find anything" and "The command manifest" (
spotify docs usage), "Web
API first: songs, episodes, shows and Liked Songs" (with "Where a start plays" and "When the
answer is lost") and "Keeping Spotify.app in the background" (spotify docs playback). New
codes inspotify docs errors:web_api_failed,device_not_found,device_restricted,
focus_not_returned.spotify playlist fork --helpsays what a copy keeps: without--name
the source's name, description, visibility and collaborative setting (a public source gives a
public copy); with--nameit is private and not collaborative. Tests now fail when a guide or
README shows a command, flag, value, config key or topic the CLI does not have, or when a help
example does not parse.