name: spotify-notes
description: Use when the user wants to save, recall, list, or search notes about a song they are listening to on the Spotify desktop app — without opening a browser or authenticating. Triggers on phrases like "note: ...", "take a note about this song", "save that thought", "what did I write about this song", "what songs have I annotated", "find my notes about [topic]", "pull up my song notes".
spotify-notes
Overview
A zero-auth journal for whatever Spotify is currently playing. Detect the current track via the local desktop player (AppleScript on macOS, playerctl on Linux), then read/write a per-track markdown file under ${CLAUDE_PLUGIN_DATA}/notes/<track-id>.md. No OAuth, no refresh tokens, no browser — the plugin talks to the Spotify desktop app over the OS's own IPC.
Core principle: the user's thought is the payload; everything else is lookup. Don't interrogate them for track names they aren't being asked to remember. Detect first, ask only if detection fails.
When to Use
- The user says anything of the form "note: X", "save that", "take a note", "remember this about this song".
- The user wants to recall what they wrote about the current track ("what did I write about this one?").
- The user wants an index of annotated songs ("what have I taken notes on", "list my song notes").
- The user wants to search across all their notes ("find notes mentioning piano", "any notes about Sufjan?").
When NOT to use:
- The user is asking about Spotify in general, using the web player, or wants Spotify API access. This plugin deliberately does not talk to the Spotify Web API.
- The user wants to stream / skip / play tracks. That's the
spotify-notes web app's job, not this plugin.
- The note isn't about a specific song ("note: I should clean the sink") — just let the main agent handle it or use the user's normal notes system.
Workflow
Pick one of four operations based on the phrasing, execute, and report concisely. Default to take-note when ambiguous — that's the most common ask.
Step 1 — Detect the current track
Always run detection first (unless the user named a specific song, in which case skip to Step 2 with that name as the query).
Use the Bash tool. On macOS (the typical case):
osascript -e 'tell application "Spotify"
if running then
set trackName to name of current track
set trackArtist to artist of current track
set trackAlbum to album of current track
set trackID to id of current track
set trackURL to spotify url of current track
set playerState to player state as string
return trackName & "\t" & trackArtist & "\t" & trackAlbum & "\t" & trackID & "\t" & trackURL & "\t" & playerState
else
return "SPOTIFY_NOT_RUNNING"
end if
end tell'
On Linux (fallback — check with which playerctl):
playerctl --player=spotify metadata --format '{{ title }}\t{{ artist }}\t{{ album }}\t{{ mpris:trackid }}\t{{ xesam:url }}\t{{ status }}' 2>/dev/null || echo "PLAYERCTL_FAILED"
Parse the tab-separated output into: title, artist, album, raw_id, url, state.
Normalize raw_id:
- macOS format:
spotify:track:4cOdK2wGLETKBW3PvgPWqT → strip spotify:track: prefix → 4cOdK2wGLETKBW3PvgPWqT
- Linux format:
/com/spotify/track/4cOdK2wGLETKBW3PvgPWqT → take last path segment → 4cOdK2wGLETKBW3PvgPWqT
Call this normalized value track_id. This is the filename (without .md).
If detection fails (output is SPOTIFY_NOT_RUNNING, empty, or PLAYERCTL_FAILED): stop and tell the user. One sentence: "Spotify isn't running / not playing anything. Start a song and ask again." Do not fall back to asking them to type the song name unless they explicitly offered one — that defeats the ergonomics.
Step 2 — Resolve the notes directory
NOTES_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.config/spotify-notes}/notes"
mkdir -p "$NOTES_DIR"
${CLAUDE_PLUGIN_DATA} is set by Claude Code when the plugin is installed; the $HOME fallback covers local dev via --plugin-dir.
Step 3 — Execute the operation
3a. Take a note (default)
The user's note text lives in what they typed after "note:" or similar. If they said "take a note" with no content, ask them what to jot down — don't invent.
The file layout is frontmatter + one H2 section per note, newest at the bottom:
---
title: "After the Storm"
artist: "Mumford & Sons"
album: "Sigh No More"
track_id: "4cOdK2wGLETKBW3PvgPWqT"
url: "spotify:track:4cOdK2wGLETKBW3PvgPWqT"
---
# After the Storm — Mumford & Sons
## 2026-04-19 14:30
That piano intro is doing so much work.
## 2026-04-19 14:32
[2:14] band re-enters — the moment.
Logic:
- Compute
FILE="$NOTES_DIR/${track_id}.md".
- If
$FILE does not exist: write the frontmatter + # {title} — {artist} heading + a blank line.
- Append
## $(date '+%Y-%m-%d %H:%M')\n$NOTE_TEXT\n to $FILE.
- Report: "Saved to
{title} — {artist}." (one line, no further commentary).
Use the Write tool for step 2 (new file) and the Bash tool with a heredoc or printf for step 3 (append). Do not read-modify-write the whole file; appending keeps concurrent note-taking from a secondary session safe.
3b. Show notes for the current song
- After detection, compute
$FILE.
- If the file exists: read it, strip the frontmatter block, render the H1 + H2 sections to the user as markdown.
- If not: "No notes yet for
{title} by {artist}." — don't create one.
3c. List annotated songs
ls -t "$NOTES_DIR"/*.md 2>/dev/null
For each file (most recent first, cap at 20 unless the user asked for more), read just the frontmatter title and artist and the file's mtime. Present as:
- After the Storm — Mumford & Sons (3 notes, last touched 2h ago)
- Jesus, Etc. — Wilco (1 note, yesterday)
Count of notes = grep -c '^## ' "$FILE". "Last touched" is the file mtime (use stat -f '%Sm' -t '%Y-%m-%d %H:%M' $FILE on macOS, stat -c '%y' $FILE on Linux).
3d. Search across all notes
The user specified a query (e.g. "piano", "Sufjan"). Use the Grep tool against $NOTES_DIR/ — not raw bash grep — so output is well-formatted for the agent to summarize.
For each hit, show {title} — {artist} (resolved from the file's frontmatter) and the matching line. Example:
After the Storm — Mumford & Sons
> held-breath piano
Jesus, Etc. — Wilco
> piano like rain
Cap at 30 hits. If zero, say so plainly.
Quick Reference
| User said… | Operation | Key command |
|---|
| "note: that piano at 2:14" | Take note | osascript → append to <track-id>.md |
| "what did I write about this song?" | Show notes | osascript → Read <track-id>.md |
| "what songs have I noted?" | List | ls -t $NOTES_DIR/*.md, parse frontmatter |
| "find my notes about piano" | Search | Grep $NOTES_DIR/ for the term |
Common Mistakes
| Mistake | Fix |
|---|
| Asking the user for the song title when they said "take a note" | Run detection first. Only ask if SPOTIFY_NOT_RUNNING. |
Writing to a relative path like ./notes/ | Use ${CLAUDE_PLUGIN_DATA}/notes/ — cwd can be anywhere. |
| Overwriting the file on every note | Append. The file grows; each note is an H2 section. |
| Using the track's title as the filename | Titles have slashes, colons, emoji. Use the normalized track_id. |
| Calling the Spotify Web API | Not the job of this plugin. Use AppleScript / playerctl only. |
Running osascript on Linux | Branch on uname -s (Darwin vs Linux), or feature-detect with which osascript / which playerctl. |
Red Flags — You're Doing It Wrong
- You're asking the user for
SPOTIFY_CLIENT_ID — the plugin doesn't authenticate.
- You're reading
/tmp or ~/Documents/spotify-notes — notes live at ${CLAUDE_PLUGIN_DATA}/notes/.
- You're re-rendering the full markdown file to add a single note — just append.
- You're prompting the user to confirm each save — writes are append-only, cheap, and reversible; just do it and report.