/verify
How to verify media.sh changes end-to-end on a real Mac — isolated data dir, real native build, and a controllable fake now-playing app (no dependency on the user's music apps).
Claude Code
$ npx -y skills add Bangs00/claude-media-control --skill verify --agent claude-code
Codex
$ npx skills add Bangs00/claude-media-control --skill verify -a codex -y
Cursor
$ npx skills add Bangs00/claude-media-control --skill verify -a cursor -y
Gemini CLI
$ npx skills add Bangs00/claude-media-control --skill verify -a gemini-cli -y
GitHub Copilot
$ npx skills add Bangs00/claude-media-control --skill verify -a github-copilot -y
OpenCode
$ npx skills add Bangs00/claude-media-control --skill verify -a opencode -y
Windsurf
$ npx skills add Bangs00/claude-media-control --skill verify -a windsurf -y
SKILL.md
verify.SKILL.mdname: verify
description: How to verify media.sh changes end-to-end on a real Mac — isolated data dir, real native build, and a controllable fake now-playing app (no dependency on the user's music apps).
Verifying claude-media-control
Surface = the `scripts/media.sh` CLI + the statusline segment it prints. `npx bats tests/media.bats` covers logic with stubs; real verification runs the actual pipeline (perl loader → adapter.dylib → mediaremoted).
Isolate from the user's live install
Always export a scratch data dir first — without it, `media.sh` falls back to `~/.claude/plugins/data/media-*` and mutates the user's real config/cache:
export CLAUDE_PLUGIN_DATA=/tmp/media-verify-data
scripts/build-native.sh --rebuild # real dylib for this checkout's version
Publish a controllable now-playing track
Don't drive Music.app via AppleScript (`tell app "Music" to …` hangs on the Automation-consent dialog in headless contexts, and you can't control track titles). Instead compile a tiny publisher: MPNowPlayingInfoCenter + MPRemoteCommandCenter handlers + a silent AVAudioEngine source node (`clang -fobjc-arc -framework Foundation -framework MediaPlayer -framework AVFoundation npub.m -o npub`). Publish any title/artist/duration you need (long titles for marquee, CJK for width handling).
Two gotchas:
- `appName`/`bundleIdentifier` only appear when the publisher runs from a
real `.app` bundle **launched via `open`** (LaunchServices registration); a bare executable publishes metadata but no app identity.
- Kill it with `pkill -f <name>`; `media.sh now` must return `null` after.
What to drive
- `media.sh now` — JSON incl. `outputDevice`, `appName`
- `media.sh statusline` after `config display.statusline on` — run twice with
`sleep 2` between to see the marquee advance; `NO_COLOR=1` and `statusline.multiline on` are cheap probes
- Statusline **wiring** (`statusline install/uninstall/status`, the
auto-wire in `config display.statusline on`, and the wrapper's uninstall self-heal) writes to `$HOME/.claude/settings.json` — export a scratch `HOME` too before driving it, or you will rewire the user's real settings. Simulate "plugin uninstalled" for the self-heal by emptying `$HOME/.claude/plugins/installed_plugins.json` (`{"plugins":{}}`) and making the recorded dev root non-executable/absent; the wrapper must restore `settings.json` and delete itself + the backup.
- `media.sh output` / `output "<substring>"` / `output <n>` — switch to the
CURRENT device for a safe no-op, or switch away and back; `output "LG"` style ambiguous names must exit 4 with candidates
- `media.sh history` / `history --json` — entries appear only on track
change (dedup across statusline ticks); drive an artist-lag amend by changing the published title first, then the artist one read later — the transitional entry must be replaced, not kept
- **cmd+click round-trip** — `build-click-handler.sh` in the scratch data
dir, then `open "claude-media-control://seek/50"` etc. and assert on the fake publisher's command log. (The legacy `claude-media://` scheme is also claimed and accepted, but the user's REAL install claims it too — `open` routing for it is ambiguous on this machine, so exercise legacy URLs by calling the scratch `click-handler.sh` directly instead of via `open`.) Two gotchas: LaunchServices launches the applet WITHOUT `CLAUDE_PLUGIN_DATA`, so rewrite the scratch `click-handler.sh` to export it (and to exec THIS checkout's `media.sh` — the resolution block prefers the marketplace cache, which may hold an older version without `open-url`). Always `build-click-handler.sh --remove` afterwards to unregister the scheme, and delete any npub entries that the user's REAL statusline (ticking every second against the real data dir) logged while the fake publisher held now-playing
- `media.sh doctor` — check the `Output dev` line and `verdict:`
`cat` may be aliased to `bat` in the user's shell — use `/bin/cat -v` to show SGR codes.
Restore
Volume, default output device, and any launched apps must match the pre-test state; the statusline cache lives in the scratch dir and dies with it.
Read more
name: verify description: How to verify media.sh changes end-to-end on a real Mac — isolated data dir, real native build, and a controllable fake now-playing app (no dependency on the user's music apps).
Verifying claude-media-control
Surface = the `scripts/media.sh` CLI + the statusline segment it prints. `npx bats tests/media.bats` covers logic with stubs; real verification runs the actual pipeline (perl loader → adapter.dylib → mediaremoted).
Isolate from the user's live install
Always export a scratch data dir first — without it, `media.sh` falls back to `~/.claude/plugins/data/media-*` and mutates the user's real config/cache:
export CLAUDE_PLUGIN_DATA=/tmp/media-verify-data scripts/build-native.sh --rebuild # real dylib for this checkout's version
Publish a controllable now-playing track
Don't drive Music.app via AppleScript (`tell app "Music" to …` hangs on the Automation-consent dialog in headless contexts, and you can't control track titles). Instead compile a tiny publisher: MPNowPlayingInfoCenter + MPRemoteCommandCenter handlers + a silent AVAudioEngine source node (`clang -fobjc-arc -framework Foundation -framework MediaPlayer -framework AVFoundation npub.m -o npub`). Publish any title/artist/duration you need (long titles for marquee, CJK for width handling).
Two gotchas:
- `appName`/`bundleIdentifier` only appear when the publisher runs from a
real `.app` bundle **launched via `open`** (LaunchServices registration); a bare executable publishes metadata but no app identity.
- Kill it with `pkill -f <name>`; `media.sh now` must return `null` after.
What to drive
- `media.sh now` — JSON incl. `outputDevice`, `appName`
- `media.sh statusline` after `config display.statusline on` — run twice with
`sleep 2` between to see the marquee advance; `NO_COLOR=1` and `statusline.multiline on` are cheap probes
- Statusline **wiring** (`statusline install/uninstall/status`, the
auto-wire in `config display.statusline on`, and the wrapper's uninstall self-heal) writes to `$HOME/.claude/settings.json` — export a scratch `HOME` too before driving it, or you will rewire the user's real settings. Simulate "plugin uninstalled" for the self-heal by emptying `$HOME/.claude/plugins/installed_plugins.json` (`{"plugins":{}}`) and making the recorded dev root non-executable/absent; the wrapper must restore `settings.json` and delete itself + the backup.
- `media.sh output` / `output "<substring>"` / `output <n>` — switch to the
CURRENT device for a safe no-op, or switch away and back; `output "LG"` style ambiguous names must exit 4 with candidates
- `media.sh history` / `history --json` — entries appear only on track
change (dedup across statusline ticks); drive an artist-lag amend by changing the published title first, then the artist one read later — the transitional entry must be replaced, not kept
- **cmd+click round-trip** — `build-click-handler.sh` in the scratch data
dir, then `open "claude-media-control://seek/50"` etc. and assert on the fake publisher's command log. (The legacy `claude-media://` scheme is also claimed and accepted, but the user's REAL install claims it too — `open` routing for it is ambiguous on this machine, so exercise legacy URLs by calling the scratch `click-handler.sh` directly instead of via `open`.) Two gotchas: LaunchServices launches the applet WITHOUT `CLAUDE_PLUGIN_DATA`, so rewrite the scratch `click-handler.sh` to export it (and to exec THIS checkout's `media.sh` — the resolution block prefers the marketplace cache, which may hold an older version without `open-url`). Always `build-click-handler.sh --remove` afterwards to unregister the scheme, and delete any npub entries that the user's REAL statusline (ticking every second against the real data dir) logged while the fake publisher held now-playing
- `media.sh doctor` — check the `Output dev` line and `verdict:`
`cat` may be aliased to `bat` in the user's shell — use `/bin/cat -v` to show SGR codes.
Restore
Volume, default output device, and any launched apps must match the pre-test state; the statusline cache lives in the scratch dir and dies with it.
The current track, live in your Claude Code statusline — ticking every second, ⌘+clickable, styleable down to the bar characters: And whatever your Mac is playing — Spotify, Apple Music, a browser tab, VLC — you can control it from the chat too: "what song is
More skills from other plugins.
browser-use
Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work. Also covers…
agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling…
Questions about verify.
- Which coding agents can use verify?
- verify installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

