Skill: codenova58/music-discovery (version 1.0.0)

--- name: music-discovery description: Mood- and context-aware music discovery—recommend tracks, build playlists, and match energy (BPM), vibe, and genre using Spotify/Last.fm-style workflows. Keywords: music recommendation, playlist, mood, Spotify, study music, workout mix. --- # Music Discovery — Mood, Scene & Playlists ## Overview Helps listeners find **tracks and playlists** that fit a **mood**, **activity**, or **taste profile**—study, commute, workout, sleep, or “something like this artist.” Use when the user wants personalized picks, scene-based sets, or exploration without manual crate-digging. **Trigger keywords**: music recommendation, playlist, mood, BPM, study music, workout, discover similar artists ## Prerequisites ```bash pip install requests spotipy ``` ## Capabilities 1. **Data-backed discovery** — Spotify Web API / Last.fm–style metadata (see `references/music_discovery_guide.md`). 2. **Scene-based sets** — work, workout, wind-down, commute, focus, party. 3. **Vibe matching** — BPM, energy, valence/mood tags, genre boundaries. ## Commands | Command | Description | Example | |---------|-------------|---------| | `recommend` | Recommend tracks | `python3 scripts/skills/music-discovery/scripts/music_discovery_tool.py recommend [args]` | | `playlist` | Build a playlist concept | `python3 scripts/skills/music-discovery/scripts/music_discovery_tool.py playlist [args]` | | `mood` | Recommend by mood | `python3 scripts/skills/music-discovery/scripts/music_discovery_tool.py mood [args]` | ## Usage (from repository root) ```bash python3 scripts/skills/music-discovery/scripts/music_discovery_tool.py recommend --scene office --mood relaxed python3 scripts/skills/music-discovery/scripts/music_discovery_tool.py playlist --scene workout --bpm 140 python3 scripts/skills/music-discovery/scripts/music_discovery_tool.py mood --feeling happy ``` ## Output format (for the agent’s report) ```markdown # Music Discovery report **Generated**: YYYY-MM-DD HH:MM ## Key picks 1. [Track / artist — one-line why] 2. … 3. … ## Snapshot | Title | Artist | Why it fits | |-------|--------|---------------| ## Playlist sketch (optional) - **Theme**: … - **Tempo / energy**: … - **Avoid**: … ## Notes [Ground claims in API or user-stated taste—no invented chart positions.] ``` ## References ### APIs & libraries - [Spotify Web API](https://developer.spotify.com/documentation/web-api) - [MusicBrainz API](https://musicbrainz.org/doc/MusicBrainz_API) - [Spotipy (Python client)](https://github.com/spotipy-dev/spotipy) ### Patterns & community - [Daily Reddit digest (OpenClaw use case)](https://github.com/hesamsheikh/awesome-openclaw-usecases/blob/main/usecases/daily-reddit-digest.md) - [Hacker News — mood-based music ML](https://news.ycombinator.com/item?id=42457780) - [Reddit r/spotify — discussion](https://www.reddit.com/r/spotify/comments/1014b31yyz/music_recommender_ai/) ## Notes - Prefer **real** API or user-provided data; do not invent popularity or audio features. - Mark missing fields as **unavailable** instead of guessing. - OAuth and rate limits apply when using Spotify—document when credentials are required.

path: data/music_discovery_data.json
content: {
  "records": [
    {
      "timestamp": "2026-03-25T15:05:28.331652",
      "command": "mood",
      "input": "",
      "status": "completed"
    }
  ],
  "created": "2026-03-25T15:05:28.331642",
  "tool": "music-discovery"
}

path: references/music_discovery_guide.md
content: # Music Discovery — Framework & Guide

## Tool summary

- **Name**: Music Discovery  
- **Commands**: `recommend`, `playlist`, `mood`  
- **Typical deps**: `pip install requests spotipy`

## Analysis dimensions

- Metadata: artist, album, genre, tempo, release era  
- Context: scene (work, gym, sleep), social vs solo  
- Audio / mood proxies: BPM, energy, mood tags (when APIs expose them)  
- Taste: seeds, “similar to,” exclusions  

## Framework

### Phase 1: Clarify the ask
- Mood, activity, language, era, explicit content on/off  
- Target length (single track vs full playlist arc)

### Phase 2: Shortlist & justify
- Prefer tracks you can ground in API results or the user’s library  
- Call out why each pick fits the stated scene or mood  

### Phase 3: Deliver a playlist shape
- Ordering (warm-up → peak → cool-down for workouts)  
- Optional diversity rules (avoid same artist back-to-back)

## Scoring rubric (fit quality)

| Score | Level | Meaning | Action |
|-------|-------|---------|--------|
| 5 | ⭐⭐⭐⭐⭐ | Strong fit | Top recommendation |
| 4 | ⭐⭐⭐⭐ | Good fit | Prioritize |
| 3 | ⭐⭐⭐ | OK | Optional |
| 2 | ⭐⭐ | Weak | Caveat |
| 1 | ⭐ | Poor | Avoid |

## Output template

```markdown
# Music Discovery analysis

## Picks
1. …
2. …

## Evidence
| Track | Artist | Source / rationale |
|-------|--------|----------------------|

## Playlist outline
- …
```

## Reference links

- [Spotify Web API](https://developer.spotify.com/documentation/web-api)  
- [MusicBrainz API](https://musicbrainz.org/doc/MusicBrainz_API)  
- [Spotipy](https://github.com/spotipy-dev/spotipy)  
- [Daily Reddit digest (OpenClaw)](https://github.com/hesamsheikh/awesome-openclaw-usecases/blob/main/usecases/daily-reddit-digest.md)  
- [Hacker News](https://news.ycombinator.com/item?id=42457780)  
- [Reddit r/spotify](https://www.reddit.com/r/spotify/comments/1014b31yyz/music_recommender_ai/)  

## Tips

1. Match **constraints** first (language, explicit, max BPM).  
2. Separate **objective metadata** from subjective “vibe” language.  
3. When APIs are unavailable, be explicit and suggest **manual** next steps (e.g. search in-app).  


path: scripts/music_discovery_tool.py
content: #!/usr/bin/env python3
"""
Music discovery — CLI stub for recommend / playlist / mood flows.

Usage:
    python3 music_discovery_tool.py recommend [args]
    python3 music_discovery_tool.py playlist [args]
    python3 music_discovery_tool.py mood [args]
"""

import json
import os
import sys
from datetime import datetime

DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "data")
DATA_FILE = os.path.join(DATA_DIR, "music_discovery_data.json")
LEGACY_DATA_FILE = os.path.join(DATA_DIR, "music_recommender_data.json")

REF_URLS = [
    "https://developer.spotify.com/documentation/web-api",
    "https://github.com/hesamsheikh/awesome-openclaw-usecases/blob/main/usecases/daily-reddit-digest.md",
    "https://musicbrainz.org/doc/MusicBrainz_API",
    "https://github.com/spotipy-dev/spotipy",
    "https://news.ycombinator.com/item?id=42457780",
]


def ensure_data_dir():
    os.makedirs(DATA_DIR, exist_ok=True)


def load_data():
    if os.path.exists(DATA_FILE):
        with open(DATA_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    if os.path.exists(LEGACY_DATA_FILE):
        with open(LEGACY_DATA_FILE, "r", encoding="utf-8") as f:
            data = json.load(f)
        data["tool"] = "music-discovery"
        save_data(data)
        return data
    return {"records": [], "created": datetime.now().isoformat(), "tool": "music-discovery"}


def save_data(data):
    ensure_data_dir()
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


def recommend(args):
    """Recommend tracks."""
    data = load_data()
    record = {
        "timestamp": datetime.now().isoformat(),
        "command": "recommend",
        "input": " ".join(args) if args else "",
        "status": "completed",
    }
    data["records"].append(record)
    save_data(data)
    return {
        "status": "success",
        "command": "recommend",
        "message": "Recommendation step completed",
        "record": record,
        "total_records": len(data["records"]),
        "reference_urls": REF_URLS[:3],
    }


def playlist(args):
    """Build playlist concept."""
    data = load_data()
    record = {
        "timestamp": datetime.now().isoformat(),
        "command": "playlist",
        "input": " ".join(args) if args else "",
        "status": "completed",
    }
    data["records"].append(record)
    save_data(data)
    return {
        "status": "success",
        "command": "playlist",
        "message": "Playlist step completed",
        "record": record,
        "total_records": len(data["records"]),
        "reference_urls": REF_URLS[:3],
    }


def mood(args):
    """Recommend by mood."""
    data = load_data()
    record = {
        "timestamp": datetime.now().isoformat(),
        "command": "mood",
        "input": " ".join(args) if args else "",
        "status": "completed",
    }
    data["records"].append(record)
    save_data(data)
    return {
        "status": "success",
        "command": "mood",
        "message": "Mood recommendation step completed",
        "record": record,
        "total_records": len(data["records"]),
        "reference_urls": REF_URLS[:3],
    }


def main():
    cmds = ["recommend", "playlist", "mood"]
    if len(sys.argv) < 2 or sys.argv[1] not in cmds:
        print(
            json.dumps(
                {
                    "error": f"Usage: music_discovery_tool.py <{','.join(cmds)}> [args]",
                    "available_commands": {c: "" for c in cmds},
                    "tool": "music-discovery",
                },
                ensure_ascii=False,
                indent=2,
            )
        )
        sys.exit(1)

    cmd = sys.argv[1]
    args = sys.argv[2:]

    if cmd == "recommend":
        result = recommend(args)
    elif cmd == "playlist":
        result = playlist(args)
    elif cmd == "mood":
        result = mood(args)
    else:
        result = {"error": f"Unknown command: {cmd}"}

    print(json.dumps(result, ensure_ascii=False, indent=2, default=str))


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)