zyx1121/music-mod
music-mod
/music: what macOS Music.app is playing, in one line above the prompt. A pressable play/pause glyph, track · artist · album, a progress bar with the clocks, and a pressable next-track glyph. One read of Music.app every five seconds, shared by every open session; the clock ticks in between.
About this mod
music-mod
/musicfor Claude Code: what Music.app is playing, in one line above the prompt, live, with play/pause and next a click away.
claude-code · mod · function-hooks · macos · music · now-playing
▶️ Tipsy · WANUKA · Greenhorn ████████████░░░░░░░░ 2:43 / 3:39 ⏭️
<sub>The band directly above the prompt, in any layout. Its clock ticks every second while shown; ▶️ and ⏭️ are clickable.</sub>
Long sessions have a soundtrack, and reaching for Music.app to check what is on breaks the flow. This mod keeps the answer one command away, inside the terminal you are already looking at: the track, where it is, and two glyphs to pause it or skip it.
It is a Claude Code mod: a plugin whose behaviour lives in a TypeScript hooks module, written against the same engine API as the built-in /diff pane. It draws into the AbovePrompt band, so it sits above the input whether the transcript is fullscreen or inline, and never takes a side dock. No shell hooks, no MCP server, and one osascript call every five seconds however many sessions are open.
Install
Function hooks are early access, so the engine loads a mod only with the flag on. Put it in your shell profile:
export CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1
Then install it as a plugin, from GitHub or from a local clone:
/plugin marketplace add zyx1121/music-mod
/plugin install music-mod@music-mod
Or try it for one session without installing:
claude --plugin-dir /path/to/music-mod
The line shows as soon as an interactive session starts. In the session:
/music hide it, or show it again
Your last choice is remembered between sessions.
macOS only: it reads Music.app through Apple events. The first read may ask you to allow your terminal to control Music.
Reading never launches Music.app, not even when a read lands while you quit it: the events go to the running process by its ID, and a process that has gone reads as closed. Only a click on the state glyph or ⏭️ talks to Music.app by name.
What it shows
One line, left to right:
| Part | Content |
|------|---------|
| ▶️ ⏸️ ⏩ ⏪ | The player state. Click it to play or pause. |
| Title | track · artist · album, cut with an ellipsis when the band is narrow |
| Bar | Progress, stretched to fill whatever width the title leaves (6 cells at the least), then position / duration |
| ⏭️ | Click it to skip to the next track. |
When Music.app is closed or stopped the band says so instead. When osascript fails (not authorized, timed out), the band shows the reason and keeps polling. A survey that takes the band is yielded to.
Configuration
| Field | Type | Default | What it does |
|-------|------|---------|--------------|
| refreshMs | number | 5000 | Milliseconds between reads of Music.app while the band is shown. Every open session shares one read per interval, and the clock ticks in between. Floored at 1000. |
| showOnStart | boolean | true | Show the line as soon as an interactive session starts. Once you have used /music, that choice wins over this. |
Set it as any plugin userConfig field: /config, or music-mod.refreshMs in settings.
How it is built
hooks/register.tsexportsregister(on, options). Onsession.startit registers/musicand shows the band (the last/musicchoice from$.store, elseshowOnStart);command.runtoggles it and remembers; a one-second$.clock.everytick redraws the clock from the last reading and, oncerefreshMshas passed (or the track has run out), takes a new one; the timer is cancelled onsession.end; a pressed glyph runs its AppleScript and reads at once.hooks/now-playing.tsholds the JXA scriptosascript -l JavaScriptruns, the parser that turns its JSON into aModel, the clock and progress-bar formatters, the position carried forward between reads, and the one-line AppleScript behind each glyph. The script asks only for what the band draws: no system volume (reading it wakescoreaudiod) and no playlist walk.hooks/shared-read.tsis the reading every session shares: a small JSON file at$TMPDIR/music-mod/now.json. A session takes the reading there when it is recent enough, else claims the file, runsosascriptand writes the result back, so the other sessions wait for it instead of running their own.hooks/views/band-view.tsxdraws theModelwith the engine'sBox,TextandButtononui.renderfor theAbovePromptcomponent.types/claude-code.d.tsis the engine contract this mod is typed against, copied fromanthropics/claude-code/mods/types.
bunx -p typescript tsc -p tsconfig.json # typecheck
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude plugin test . # 35 tests, the engine's own harness
The API these mods are written against may change between releases without notice. When it does, refresh types/claude-code.d.ts from upstream and let the typecheck point at what moved.
Contributing
Issues and PRs are welcome. Ground rules live in CONTRIBUTING.md.
License
MIT · now playing: whatever you left on
Installation
Check the author's README for the marketplace and plugin name first. Commands may change as the repository evolves.
claude plugin marketplace add zyx1121/music-mod claude plugin install music-mod
Original text / README
music-mod
/musicfor Claude Code: what Music.app is playing, in one line above the prompt, live, with play/pause and next a click away.
claude-code · mod · function-hooks · macos · music · now-playing
▶️ Tipsy · WANUKA · Greenhorn ████████████░░░░░░░░ 2:43 / 3:39 ⏭️
<sub>The band directly above the prompt, in any layout. Its clock ticks every second while shown; ▶️ and ⏭️ are clickable.</sub>
Long sessions have a soundtrack, and reaching for Music.app to check what is on breaks the flow. This mod keeps the answer one command away, inside the terminal you are already looking at: the track, where it is, and two glyphs to pause it or skip it.
It is a Claude Code mod: a plugin whose behaviour lives in a TypeScript hooks module, written against the same engine API as the built-in /diff pane. It draws into the AbovePrompt band, so it sits above the input whether the transcript is fullscreen or inline, and never takes a side dock. No shell hooks, no MCP server, and one osascript call every five seconds however many sessions are open.
Install
Function hooks are early access, so the engine loads a mod only with the flag on. Put it in your shell profile:
export CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1
Then install it as a plugin, from GitHub or from a local clone:
/plugin marketplace add zyx1121/music-mod
/plugin install music-mod@music-mod
Or try it for one session without installing:
claude --plugin-dir /path/to/music-mod
The line shows as soon as an interactive session starts. In the session:
/music hide it, or show it again
Your last choice is remembered between sessions.
macOS only: it reads Music.app through Apple events. The first read may ask you to allow your terminal to control Music.
Reading never launches Music.app, not even when a read lands while you quit it: the events go to the running process by its ID, and a process that has gone reads as closed. Only a click on the state glyph or ⏭️ talks to Music.app by name.
What it shows
One line, left to right:
| Part | Content |
|------|---------|
| ▶️ ⏸️ ⏩ ⏪ | The player state. Click it to play or pause. |
| Title | track · artist · album, cut with an ellipsis when the band is narrow |
| Bar | Progress, stretched to fill whatever width the title leaves (6 cells at the least), then position / duration |
| ⏭️ | Click it to skip to the next track. |
When Music.app is closed or stopped the band says so instead. When osascript fails (not authorized, timed out), the band shows the reason and keeps polling. A survey that takes the band is yielded to.
Configuration
| Field | Type | Default | What it does |
|-------|------|---------|--------------|
| refreshMs | number | 5000 | Milliseconds between reads of Music.app while the band is shown. Every open session shares one read per interval, and the clock ticks in between. Floored at 1000. |
| showOnStart | boolean | true | Show the line as soon as an interactive session starts. Once you have used /music, that choice wins over this. |
Set it as any plugin userConfig field: /config, or music-mod.refreshMs in settings.
How it is built
hooks/register.tsexportsregister(on, options). Onsession.startit registers/musicand shows the band (the last/musicchoice from$.store, elseshowOnStart);command.runtoggles it and remembers; a one-second$.clock.everytick redraws the clock from the last reading and, oncerefreshMshas passed (or the track has run out), takes a new one; the timer is cancelled onsession.end; a pressed glyph runs its AppleScript and reads at once.hooks/now-playing.tsholds the JXA scriptosascript -l JavaScriptruns, the parser that turns its JSON into aModel, the clock and progress-bar formatters, the position carried forward between reads, and the one-line AppleScript behind each glyph. The script asks only for what the band draws: no system volume (reading it wakescoreaudiod) and no playlist walk.hooks/shared-read.tsis the reading every session shares: a small JSON file at$TMPDIR/music-mod/now.json. A session takes the reading there when it is recent enough, else claims the file, runsosascriptand writes the result back, so the other sessions wait for it instead of running their own.hooks/views/band-view.tsxdraws theModelwith the engine'sBox,TextandButtononui.renderfor theAbovePromptcomponent.types/claude-code.d.tsis the engine contract this mod is typed against, copied fromanthropics/claude-code/mods/types.
bunx -p typescript tsc -p tsconfig.json # typecheck
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude plugin test . # 35 tests, the engine's own harness
The API these mods are written against may change between releases without notice. When it does, refresh types/claude-code.d.ts from upstream and let the typecheck point at what moved.
Contributing
Issues and PRs are welcome. Ground rules live in CONTRIBUTING.md.
License
MIT · now playing: whatever you left on

