Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
60ba86e
feat(installer): spotify-login with PKCE, loopback and a paste file
NovusEdge Oct 13, 2026
51ca5c4
fix(installer): spotify token write and login exit
NovusEdge Oct 13, 2026
c06600a
feat(installer): spotify-refresh behind a lock, and spotify-logout
NovusEdge Oct 13, 2026
575a965
fix(installer): spotify-logout takes the refresh lock
NovusEdge Oct 13, 2026
e70d1c9
feat(wiring): Host.fetch with a timeout and Host.spawn by lines
NovusEdge Oct 13, 2026
9505411
fix(wiring): spawn reports onLine errors and stops at kill
NovusEdge Oct 13, 2026
e005ecb
feat(wiring): Spotify token file, Web API requests and parsers
NovusEdge Oct 13, 2026
b4ac742
fix(wiring): token keeper remembers a refused file
NovusEdge Oct 13, 2026
3c8ed70
feat(wiring): Spotify API client and which source the player shows
NovusEdge Oct 13, 2026
b1c00da
fix(wiring): hold the endpoint until its request settles; lag, floor …
NovusEdge Oct 13, 2026
f3dee8d
fix(wiring): the bar follows a real seek during fallback
NovusEdge Oct 13, 2026
b5fa00e
feat(pane): the heart, Up next, and the Spotify connect column
NovusEdge Oct 13, 2026
0a0fadc
test: the music wiring rig moves to test/spotify-kit.ts, with the Web…
NovusEdge Oct 13, 2026
e5de82b
feat(commands): connect Spotify; the Web API runs the player when it …
NovusEdge Oct 13, 2026
02ee14a
fix(wiring): a closed dialog stops a starting login
NovusEdge Oct 13, 2026
45c83ea
docs: connecting Spotify
NovusEdge Oct 13, 2026
e453b11
fix(wiring): one paste file per login; lost() only from ok; busy, rat…
NovusEdge Oct 13, 2026
a5a8a65
docs: Linux volume while paused, Web API controls, damaged token file…
NovusEdge Oct 13, 2026
59d447c
fix(pane): the connect fields take the keyboard
NovusEdge Oct 13, 2026
fdc49a9
fix(wiring): fall back only when Spotify refused
NovusEdge Oct 13, 2026
a4455a3
fix(pane): the paste field takes the keyboard after the Client ID
NovusEdge Oct 13, 2026
b06cedd
chore(docket): record the Spotify connect decisions and engine findings
NovusEdge Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .docket/ledger.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,9 @@
{"schema":3,"kind":"claim","id":"c24","text":"Spotify 1.2.74 on Linux ignores an MPRIS Volume write while paused: set-property Volume d 0.5 reads back 1.0 a second later","state":"accepted","ts":"2026-10-12T11:53:35+00:00","author":"claude-code","session":"session_01BEQogNE1Zk8GftfwcMBNAN","branch":"feat/music-dialog","scope":["hooks/music/reader.ts"],"rationale":"decides whether the dialog offers a local volume bar on Linux","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"busctl on the owner's machine, 2026-10-12"}],"revisit":"a Spotify release that honours Volume writes, or a check while playing","cost_if_wrong":"a volume bar that does nothing on Linux, or one hidden where it would work","pinned":false}
{"schema":3,"kind":"decision","id":"d123","text":"Music dialog layout breakpoints: the 20x10 cover needs 20 body rows","state":"adopted","ts":"2026-10-13T06:21:49+00:00","author":"claude-code","session":"session_01UpYey2RhLeNfBnAiprrv4G","branch":"feat/music-dialog","scope":["hooks/musicplayer.tsx"],"rationale":"on macOS the left column under a 20x10 cover needs about 20 rows (cover, gap, three track rows, seek and volume bars, controls margin, up to three wrapped button rows), so 18 rows clipped the controls","supports":[["c23"]],"depends_on":[],"answers":[],"supersedes":["d122"],"evidence":[],"revisit":"","cost_if_wrong":"Linux at 18-19 rows shows the 8x4 cover where 20x10 would fit","pinned":false,"supersede_reason":"revise","choice":"Side by side from 80 granted columns; the cover is 20x10 from 20 body rows, 8x4 from 10, none below 10","alternatives":[],"decided_by":""}
{"schema":3,"kind":"decision","id":"d124","text":"/glowup music toggles the music dialog","state":"adopted","ts":"2026-10-13T06:21:49+00:00","author":"claude-code","session":"session_01UpYey2RhLeNfBnAiprrv4G","branch":"feat/music-dialog","scope":["hooks/register.tsx"],"rationale":"the test engine cannot raise Esc, and the toggle matches an existing command","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"a Close button instead","pinned":false,"choice":"Running /glowup music while the dialog is shown closes it, as /glowup pane does","alternatives":[],"decided_by":""}
{"schema":3,"kind":"claim","id":"c25","text":"On Claude Code 2.1.296 a mod's process.spawn exposes no pid, signal or process group, so a mod cannot kill a spawned helper's children","state":"unassessed","ts":"2026-10-13T11:08:10+00:00","author":"claude-code","session":"session_01UpYey2RhLeNfBnAiprrv4G","branch":"feat/music-connect","scope":[],"rationale":"decides what Cancel can promise for spotify-login","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"a cancelled login could be stopped and the docs overstate the limit","pinned":false}
{"schema":3,"kind":"claim","id":"c26","text":"On Claude Code 2.1.296 the dialog focus ring keeps a position in the element order, not an element; after a step change it can land on nothing and the next typed letter goes to the composer; autoFocus applies only when a site takes the keys","state":"unassessed","ts":"2026-10-13T11:08:11+00:00","author":"claude-code","session":"session_01UpYey2RhLeNfBnAiprrv4G","branch":"feat/music-connect","scope":[],"rationale":"decides how the connect dialog moves focus between steps","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"fields that cannot be reached from the keyboard","pinned":false}
{"schema":3,"kind":"decision","id":"d125","text":"A failed Spotify Web API write falls back to the local app only on a 429","state":"adopted","ts":"2026-10-13T11:08:11+00:00","author":"claude-code","session":"session_01UpYey2RhLeNfBnAiprrv4G","branch":"feat/music-connect","scope":["hooks/register.tsx"],"rationale":"after a 5xx or a timeout the write may have landed; a local Next would skip twice and MPRIS PlayPause would toggle an API pause back to playing","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"a press during an API outage needs a second press","pinned":false,"choice":"429 and explicit refusals fall back to the local app; a 5xx, a timeout or a busy endpoint shows 'Spotify is not answering right now. Try again.'","alternatives":["fall back to local on any failed write"],"decided_by":""}
{"schema":3,"kind":"decision","id":"d126","text":"Each Spotify login gets its own paste file","state":"adopted","ts":"2026-10-13T11:08:11+00:00","author":"claude-code","session":"session_01UpYey2RhLeNfBnAiprrv4G","branch":"feat/music-connect","scope":["hooks/music/tokens.ts"],"rationale":"a cancelled login's helper lives on for minutes and would consume the next login's pasted address","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"stray paste files after a crash","pinned":false,"choice":"spotify-paste-<session>-<seq>, removed with its .tmp when the login ends or is cancelled","alternatives":["one shared spotify-paste file"],"decided_by":""}
{"schema":3,"kind":"decision","id":"d127","text":"A Spotify Web API endpoint stays busy until the engine request settles","state":"adopted","ts":"2026-10-13T11:08:11+00:00","author":"claude-code","session":"session_01UpYey2RhLeNfBnAiprrv4G","branch":"feat/music-connect","scope":["hooks/music/spotify.ts"],"rationale":"the engine request cannot be aborted, so freeing the key at the timeout let requests pile up one per 5 s","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"an engine request that never settles blocks that endpoint until reload","pinned":false,"choice":"SpotifyClient holds an endpoint's key until FetchInit.onSettled fires; the caller still gets offline after 5 s and falls back to local","alternatives":["free the key at the 5 s timeout"],"decided_by":""}
{"schema":3,"kind":"decision","id":"d128","text":"The connect dialog moves focus to the paste field with ui.focus when the waiting step draws","state":"adopted","ts":"2026-10-13T11:08:12+00:00","author":"claude-code","session":"session_01UpYey2RhLeNfBnAiprrv4G","branch":"feat/music-connect","scope":["hooks/register.tsx","hooks/musicplayer.tsx"],"rationale":"the focus ring keeps its position across the step change and landed on nothing","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"the paste field is unreachable from the keyboard over SSH","pinned":false,"choice":"startLogin calls $.ui.focus on the paste Input after publishing the waiting step; Copy address draws below the field","alternatives":[],"decided_by":""}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `/glowup music`, or `o: Player` on the Music tab, opens a music player: a pane with a bigger cover, the seek bar, the controls, Open Spotify (`o`) and Copy link (`c`), and on macOS a volume bar.
- `/glowup spotify connect` connects your own Spotify developer app (Premium needed). The Music tab and player then follow playback on any device, show a heart to like the track (`l`) and the next track in your queue, and fall back to the local app while the Web API is not answering. Windows gets the player this way. See docs/spotify.md.

## [1.1.0] - 2026-10-12

Expand Down
4 changes: 4 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ Setting `theme` back to `classic`, `spinner` to `pack` or `statusline` to its de

`/glowup music` opens the music player, a pane that takes the keyboard; Esc closes it, and running the command again while the player is open closes it too, as `/glowup pane` does. When there is no room for the player, the command prints `Music player waits: <reason>`. See [Layout](layout.md#music-player). Opening it from the Music tab's `o: Player` closes the glowup pane while the player is up and brings it back when the player closes. In a `-p` run it prints the help.

## spotify

`/glowup spotify` says whether glowup reads the Spotify app on this computer or your account through the Web API. `/glowup spotify connect` opens the music player at the connect steps (in a `-p` run it says it needs an interactive session); `/glowup spotify disconnect` deletes the saved login. See [Connect Spotify](spotify.md).

## motion

| Command | What it does |
Expand Down
9 changes: 5 additions & 4 deletions docs/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,20 @@ The track playing in the Spotify desktop app, in a `NOW PLAYING` box. The tab st

- The box shows the cover beside the title, the artist and album, and a progress bar. Below 40 columns the cover is left out. The cover is Spotify's 64 px image, drawn in half-block pixels by glowup's helper binary, which glowup downloads the first time it needs one (the same binary `/glowup config` uses). Until it arrives, or if it cannot, the box shows a `♪`.
- Click the progress bar to jump to that point, or drag along it and let go.
- Below the box: `r: Prev`, `p: Pause` (`Play` while paused), `n: Next`, `s: ⇄` (shuffle), `e: ↻` (repeat, off and on) and `o: Player`. Shuffle and repeat are macOS only: Spotify's Linux client ignores repeat and shuffle-on changes over MPRIS, so those buttons are left out there. Shuffle and repeat are bright when on and dim when off, and are left out when the player does not report them. Click a button, or press its key while the pane has the keyboard. They act on the Spotify on this machine.
- Below the box: `r: Prev`, `p: Pause` (`Play` while paused), `n: Next`, `s: ⇄` (shuffle), `e: ↻` (repeat, off and on) and `o: Player`. Shuffle and repeat are macOS only: Spotify's Linux client ignores repeat and shuffle-on changes over MPRIS, so those buttons are left out there. Shuffle and repeat are bright when on and dim when off, and are left out when the player does not report them. Click a button, or press its key while the pane has the keyboard. They act through the Web API when [Spotify is connected](spotify.md), otherwise on the Spotify app on this machine.
- With [Spotify connected](spotify.md), a heart ends the title row: `♥` when the track is in your library, `♡` when not. `l: ♥` below toggles it. The third row shows `Up next: <track> — <artist>`, or `on: <device>` when another device plays. The header reads `Spotify (local)` while the Web API is not answering and glowup shows the app on this computer. While the Web API runs the player, shuffle and repeat (every mode) are available on Linux too.
- `o: Player` opens the music player, a focused pane with a bigger cover. Esc closes it and gives the keys back to the glowup pane.
- An ad shows `Ad` in place of the track. With Spotify closed the tab says `Open Spotify to see what's playing.`
- glowup reads the player locally, every second while the tab is open and playing, otherwise every 2 to 10 seconds; the bar moves on its own between reads. On Linux it uses `busctl` (systemd) and falls back to `playerctl`; when neither is installed the tab says what to install. On macOS it uses `osascript`, and only asks when Spotify is already running, so glowup never launches it. Windows is not supported.
- glowup reads the player locally, every second while the tab is open and playing, otherwise every 2 to 10 seconds; the bar moves on its own between reads. On Linux it uses `busctl` (systemd) and falls back to `playerctl`; when neither is installed the tab says what to install. On macOS it uses `osascript`, and only asks when Spotify is already running, so glowup never launches it. Windows needs [Spotify connected](spotify.md).
- While Spotify plays, the pet dances with a speaker beside it, and wears headphones and dances while Claude works. Custom pets without a dance use their idle and working poses. `/glowup setup pet.dance off` turns this off.
- The compact form is one line: `♪ title — artist ▶`.

### Music player

`/glowup music`, or `o` on the Music tab, opens the player. It shows the cover at 20 by 10 cells when the pane has 20 body rows or more, 8 by 4 from 10 rows, and none below 10. It also shows the title, artist and album, and the seek bar, which you click or drag as on the tab. Below it are the same Prev, Play, Next, shuffle and repeat keys, `o: Open Spotify` to bring Spotify's window forward (on Linux with busctl, and on macOS), and `c: Copy link` to copy the track's `open.spotify.com` link.

On macOS a volume bar sits under the seek bar: click or drag it, or click it and use the left and right arrows. Spotify 1.2.74 on Linux ignored a volume write over MPRIS (observed while paused), so Linux has no volume bar.
On macOS a volume bar sits under the seek bar: click or drag it, or click it and use the left and right arrows. Spotify 1.2.74 on Linux ignored a volume write over MPRIS (observed while paused), so Linux has no volume bar unless [Spotify is connected](spotify.md) and its Web API runs the player.

From 80 columns the player sits beside a second column, which will hold your queue, recent tracks, playlists and devices once you connect Spotify. Narrower, the two stack. While the player is open, toasts wait until it closes; anything the player has to tell you, such as a copied link, shows inside it.
From 80 columns the player sits beside a second column; narrower, the two stack. Until you connect Spotify, the column offers `k: Connect`, which walks through [connecting](spotify.md) in place. Once connected it shows your account, Up next and the device playing; the queue, recent tracks, playlists and devices come in a later release. While the player is open, toasts wait until it closes; anything the player has to tell you, such as a copied link or a refused command, shows inside it.

The tabs do not respond to selection yet; opening a subagent's tool calls is on the [roadmap](roadmap.md).
2 changes: 1 addition & 1 deletion docs/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "glowup",
"pages": ["---Start---", "install", "---Use---", "commands", "layout", "statusline", "pets", "pet-sprites", "---Packs---", "packs", "pack-reference", "---Themes---", "themes", "theme-reference", "---More---", "accessibility", "roadmap"]
"pages": ["---Start---", "install", "---Use---", "commands", "layout", "spotify", "statusline", "pets", "pet-sprites", "---Packs---", "packs", "pack-reference", "---Themes---", "themes", "theme-reference", "---More---", "accessibility", "roadmap"]
}
58 changes: 58 additions & 0 deletions docs/spotify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Connect Spotify
description: Connect your own Spotify developer app so the Music tab and player show your queue, likes and phone playback.
---

Without connecting, glowup reads the Spotify app on this computer. Connecting your Spotify account through the Web API adds:

- a heart on the Music tab and in the player, to like the track playing (`l`)
- Up next, the first track in your queue
- playback on any device (your phone, a speaker, another computer), with the tab and the controls following it
- shuffle, every repeat mode and volume while the Web API runs the player, including on Linux, where Spotify's app ignores shuffle-on, repeat and (in 1.2.74, while paused) volume writes from glowup
- the player on Windows, where glowup cannot read the local app

## What you need

- **Spotify Premium.** Spotify lets an app control playback only for Premium accounts, and a developer app in development mode only for a Premium owner.
- **A free Spotify developer app of your own.** Spotify no longer gives one app to many people: an app in development mode serves up to five people its owner adds, so glowup cannot ship a shared one. Making your own takes a couple of minutes.

## Connect

1. Run `/glowup spotify connect`. The music player opens with the steps in its right column. The Connect button (`k`) in that column opens the same steps.
2. Open https://developer.spotify.com/dashboard, log in, and create an app. Any name and description will do.
3. Tick **Web API**.
4. Add the redirect URI `http://127.0.0.1/callback`, exactly so: `127.0.0.1`, not `localhost`, and no port. Save.
5. Copy the app's **Client ID** into the player's Client ID field and press Enter. It is 32 hexadecimal characters.
6. Your browser opens Spotify's consent page. Agree, and the player says **Connected as** your name.

In the steps, `d` copies the dashboard address and `u` the redirect URI. The connect steps need an interactive session; in a `-p` run the command says so.

### Over SSH, or with the browser on another machine

After you agree, the browser goes to `http://127.0.0.1:<port>/callback?…`, which only the machine running glowup can open, so it shows an error page. Copy that whole address from the browser's address bar into **Or paste the address the browser ended on** and press Enter. glowup checks that it came from this login before using it, and shows the reason in the player if it did not.

While glowup waits, `w` copies the consent page's address in case the browser did not open by itself. The login gives up after five minutes.

### Cancelling

`x` cancels the login, and closing the player does the same. glowup then stops waiting, but the helper process behind the login cannot be stopped from inside glowup and may keep waiting for the browser until its five minutes are up. If you agree on Spotify's page in that time anyway, the login still completes and writes the file, and glowup connects at its next start.

## Where the login lives

glowup keeps the login in `~/.claude/glowup/spotify.json` (under `$CLAUDE_CONFIG_DIR` if you set it), readable only by you (mode 0600). Only glowup's helper program, `glowup-installer`, writes that file, and the login never goes into glowup's settings store, the status line or a log. For your account glowup talks only to `accounts.spotify.com` and `api.spotify.com`; covers come from Spotify's image server, as they do without connecting.

## Disconnect

`/glowup spotify disconnect` deletes the file, and glowup goes back to the local app. To revoke glowup entirely, delete the app on the developer dashboard too.

`/glowup spotify` says which you are on: the app on this computer, or your account (with the start of the Client ID and your name), or that you need to connect again.

## When something is off

- **`Spotify (local)` in the Music tab's header.** The Web API is not answering (offline, or Spotify having trouble), so glowup shows the app on this computer until it does. Nothing to do.
- **"Spotify playback control needs Premium, so glowup uses the Spotify app on this computer for it."** Spotify answered that the account is not Premium. For the rest of the session transport (play, pause, skip, seek) goes to the local app; the heart and Up next still use the Web API. On Linux, shuffle, repeat and volume are back to what the local app takes.
- **"Nothing is playing. Pick a device."** No device has Spotify active. Start something in any Spotify app.
- **"Spotify needs you to connect again."** The login expired or you removed glowup's access. Run `/glowup spotify connect`.
- **"…was saved by a newer glowup".** A newer glowup wrote the login file. Update glowup; this one leaves the file alone and uses the local app.
- **"…is damaged. /glowup spotify disconnect removes it; then connect again."** The login file cannot be read as a login. Run `/glowup spotify disconnect`, then `/glowup spotify connect`.
- **The browser says `INVALID_CLIENT: Invalid redirect URI`.** The app's redirect URI is not exactly `http://127.0.0.1/callback`. Fix it on the dashboard and connect again.
Loading
Loading