# Spotify

> spotify

- Skill: `yakeworld/spotify` (Agent Skill, multi-file: 11 files)
- Install (CLI): `npx skillmds@latest add yakeworld/spotify`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yakeworld/spotify/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: yakeworld (https://skillmd.com/u/yakeworld)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/yakeworld/spotify

---



## Operational Steps
1. 确认输入参数完整
2. 执行核心操作（参考本目录下的 scripts/ 或 references/）
3. 验证输出符合契约
4. 保存结果并报告

## Pitfalls
- 
- 

## Verification
- 
- 
- 
- 
1. 
2. 
3. 
## IO_CONTRACT

- **input**: `request: str, context: dict` — 用户请求描述、上下文信息
- **output**: `result: dict — 技能执行结果（结构因技能而异）`

> 对应原则：P2（机械原子暴露输入输出规范）

# Spotify

Control the user's Spotify account via the Hermes Spotify toolset (7 tools). Setup guide: https://hermes-agent.nousresearch.com/docs/user-guide/features/spotify

## When to use this skill

The user says something like "play X", "pause", "skip", "queue up X", "what's playing", "search for X", "add to my X playlist", "make a playlist", "save this to my library", etc.

## The 7 tools

- `spotify_playback` — play, pause, next, previous, seek, set_repeat, set_shuffle, set_volume, get_state, get_currently_playing, recently_played
- `spotify_devices` — list, transfer
- `spotify_queue` — get, add
- `spotify_search` — search the catalog
- `spotify_playlists` — list, get, create, add_items, remove_items, update_details
- `spotify_albums` — get, tracks
- `spotify_library` — list/save/remove with `kind: "tracks"|"albums"`

Playback-mutating actions require Spotify Premium; search/library/playlist ops work on Free.

## Canonical patterns (minimize tool calls)

### "Play <artist/track/album>"
One search, then play by URI. Do NOT loop through search results describing them unless the user asked for options.

```
spotify_search({"query": "miles davis kind of blue", "types": ["album"], "limit": 1})
→ got album URI spotify:album:1weenld61qoidwYuZ1GESA
spotify_playback({"action": "play", "context_uri": "spotify:album:1weenld61qoidwYuZ1GESA"})
```

For "play some <artist>" (no specific song), prefer `types: ["artist"]` and play the artist context URI — Spotify handles smart shuffle. If the user says "the song" or "that track", search `types: ["track"]` and pass `uris: [track_uri]` to play.

### "What's playing?" / "What am I listening to?"
Single call — don't chain get_state after get_currently_playing.

```
spotify_playback({"action": "get_currently_playing"})
```

If it returns 204/empty (`is_playing: false`), tell the user nothing is playing. Don't retry.

### "Pause" / "Skip" / "Volume 50"
Direct action, no preflight inspection needed.

```
spotify_playback({"action": "pause"})
spotify_playback({"action": "next"})
spotify_playback({"action": "set_volume", "volume_percent": 50})
```

### "Add to my <playlist name> playlist"
1. `spotify_playlists list` to find the playlist ID by name
2. Get the track URI (from currently playing, or search)
3. `spotify_playlists add_items` with the playlist_id and URIs

```
spotify_playlists({"action": "list"})
→ found "Late Night Jazz" = 37i9dQZF1DX4wta20PHgwo
spotify_playback({"action": "get_currently_playing"})
→ current track uri = spotify:track:0DiWol3AO6WpXZgp0goxAV
spotify_playlists({"action": "add_items",
                   "playlist_id": "37i9dQZF1DX4wta20PHgwo",
                   "uris": ["spotify:track:0DiWol3AO6WpXZgp0goxAV"]})
```

### "Create a playlist called X and add the last 3 songs I played"
```
spotify_playback({"action": "recently_played", "limit": 3})
spotify_playlists({"action": "create", "name": "Focus 2026"})
→ got playlist_id back in response
spotify_playlists({"action": "add_items", "playlist_id": <id>, "uris": [<3 uris>]})
```

### "Save / unsave / is this saved?"
Use `spotify_library` with the right `kind`.

```
spotify_library({"kind": "tracks", "action": "save", "uris": ["spotify:track:..."]})
spotify_library({"kind": "albums", "action": "list", "limit": 50})
```

### "Transfer playback to my <device>"
```
spotify_devices({"action": "list"})
→ pick the device_id by matching name/type
spotify_devices({"action": "transfer", "device_id": "<id>", "play": true})
```

## Critical failure modes

**`403 Forbidden — No active device found`** on any playback action means Spotify isn't running anywhere. Tell the user: "Open Spotify on your phone/desktop/web player first, start any track for a second, then retry." Don't retry the tool call blindly — it will fail the same way. You can call `spotify_devices list` to confirm; an empty list means no active device.

**`403 Forbidden — Premium required`** means the user is on Free and tried to mutate playback. Don't retry; tell them this action needs Premium. Reads still work (search, playlists, library, get_state).

**`204 No Content` on `get_currently_playing`** is NOT an error — it means nothing is playing. The tool returns `is_playing: false`. Just report that to the user.

**`429 Too Many Requests`** = rate limit. Wait and retry once. If it keeps happening, you're looping — stop.

**`401 Unauthorized` after a retry** — refresh token revoked. Tell the user to run `hermes auth spotify` again.

## URI and ID formats

Spotify uses three interchangeable ID formats. The tools accept all three and normalize:

- URI: `spotify:track:0DiWol3AO6WpXZgp0goxAV` (preferred)
- URL: `https://open.spotify.com/track/0DiWol3AO6WpXZgp0goxAV`
- Bare ID: `0DiWol3AO6WpXZgp0goxAV`

When in doubt, use full URIs. Search results return URIs in the `uri` field — pass those directly.

Entity types: `track`, `album`, `artist`, `playlist`, `show`, `episode`. Use the right type for the action — `spotify_playback.play` with a `context_uri` expects album/playlist/artist; `uris` expects an array of track URIs.

## What NOT to do

- **Don't call `get_state` before every action.** Spotify accepts play/pause/skip without preflight. Only inspect state when the user asked "what's playing" or you need to reason about device/track.
- **Don't describe search results unless asked.** If the user said "play X", search, grab the top URI, play it. They'll hear it's wrong if it's wrong.
- **Don't retry on `403 Premium required` or `403 No active device`.** Those are permanent until user action.
- **Don't use `spotify_search` to find a playlist by name** — that searches the public Spotify catalog. User playlists come from `spotify_playlists list`.
- **Don't mix `kind: "tracks"` with album URIs** in `spotify_library` (or vice versa). The tool normalizes IDs but the API endpoint differs.

## 验证清单 · VERIFICATION

- [ ] 播放/库操作前确认 Spotify 有活跃设备（`403 No active device` 出现时停止重试并提示用户先启动客户端）
- [ ] 播放类变更操作符合 Premium 要求，`403 Premium required` 时不盲目重试，明确告知需升级
- [ ] 传入的 ID 使用正确的类型化 URI（track/album/artist/playlist），`spotify_library` 的 `kind` 与 URI 类型一致
- [ ] 找用户私有播放列表用 `spotify_playlists list`（而非 `spotify_search` 公共目录），"正在播放"用单次 `get_currently_playing` 且 204 视为无播放而非错误
- [ ] 遇 `429 Too Many Requests` 等待后仅重试一次；`401 Unauthorized` 提示用户重新 `hermes auth spotify`

## 约束规则 · RULES

1. **输入约束**: 参数类型、范围、格式必须校验
2. **输出约束**: 返回值结构、编码、命名必须一致
3. **异常约束**: 错误信息必须包含上下文和恢复建议
4. **安全约束**: 不执行未验证的任意代码，不暴露内部状态

## Golden 集合 · GOLDEN SET

- **Golden Input**: 标准输入样本（覆盖正常路径）
- **Golden Output**: 预期输出（精确匹配或格式校验）
- **Golden Error**: 预期错误信息（覆盖失败路径）

> Golden 集合是测试的单一真理来源。所有改进必须通过 golden 测试。

> 违反规则的操作视为不安全，必须拒绝或隔离。

> 每项验证必须可执行、可记录、可复现。验证失败时记录原因和修复。

## Genes (策略基因)

> 紧凑策略表示。条件→策略。需要深度时参考完整文档。

- **[SPOT-001]** 用户请求播放特定内容 → 执行单次搜索获取 URI 后立即播放，禁止循环描述搜索结果或进行预检状态查询
- **[SPOT-002]** 用户请求模糊播放（如“播放某艺术家”） → 优先使用 `types: ["artist"]` 搜索并播放艺术家上下文 URI，利用 Spotify 智能洗牌而非手动选曲
- **[SPOT-003]** 执行播放、暂停或音量调整等变更操作 → 直接调用对应动作接口，无需先调用 `get_state` 进行预检
- **[SPOT-004]** 遇到 `403 No active device` 或 `403 Premium required` 错误 → 立即停止重试，明确告知用户需启动 Spotify 客户端或升级 Premium 套餐
- **[SPOT-005]** 查询当前播放状态返回 `204 No Content` 或 `is_playing: false` → 直接报告“无内容播放”，视为正常状态而非错误，禁止重试
- **[SPOT-006]** 需要查找用户私有播放列表 → 必须使用 `spotify_playlists list` 接口，严禁使用 `spotify_search` 搜索公共目录
- **[SPOT-007]** 操作用户库（Library）保存或移除项目 → 严格匹配 `kind` 参数（tracks/albums）与 URI 类型，禁止混用导致 API 端点错误


## 示例 · EXAMPLES

### 示例 1：播放指定专辑
- 输入: "play miles davis kind of blue"
- 操作: `spotify_search({"query": "miles davis kind of blue", "types": ["album"], "limit": 1})` 取 URI → `spotify_playback({"action": "play", "context_uri": "<album_uri>"})`
- 验证: 播放成功（无 403）；`get_currently_playing` 返回该专辑

### 示例 2：把当前歌曲加入私有播放列表
- 输入: "add this to my Late Night Jazz playlist"
- 操作: `spotify_playlists list` 按名找 playlist_id → `get_currently_playing` 取 track URI → `add_items`
- 验证: 返回成功且 track 出现在该列表（勿用 `spotify_search` 找用户播放列表）

### 示例 3：无活跃设备时的失败处理
- 输入: "pause" 但 Spotify 未运行
- 操作: `spotify_playback({"action": "pause"})` 返回 `403 No active device`
- 验证: 停止重试，提示用户先启动 Spotify 客户端；可用 `spotify_devices list` 确认为空列表

