From 5500b5180034693dfb77a0553079cd25b3d8dd98 Mon Sep 17 00:00:00 2001 From: Austin Pickett Date: Fri, 24 Apr 2026 12:32:10 -0400 Subject: [PATCH] chore: fix lint --- skills/mlops/models/segment-anything/SKILL.md | 2 + .../research/research-paper-writing/SKILL.md | 2 + website/docs/developer-guide/architecture.md | 2 +- .../docs/developer-guide/gateway-internals.md | 2 +- website/docs/reference/skills-catalog.md | 1 + .../skills/bundled/media/media-spotify.md | 150 ++++++++++++++++++ .../mlops/mlops-models-segment-anything.md | 2 + .../bundled/mlops/mlops-training-unsloth.md | 2 +- .../research-research-paper-writing.md | 2 + website/scripts/generate-skill-docs.py | 8 + website/sidebars.ts | 1 + 11 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 website/docs/user-guide/skills/bundled/media/media-spotify.md diff --git a/skills/mlops/models/segment-anything/SKILL.md b/skills/mlops/models/segment-anything/SKILL.md index 14b766e5b..2fea76141 100644 --- a/skills/mlops/models/segment-anything/SKILL.md +++ b/skills/mlops/models/segment-anything/SKILL.md @@ -134,6 +134,7 @@ masks = processor.image_processor.post_process_masks( ### Model architecture + ``` SAM Architecture: ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ @@ -144,6 +145,7 @@ SAM Architecture: Image Embeddings Prompt Embeddings Masks + IoU (computed once) (per prompt) predictions ``` + ### Model variants diff --git a/skills/research/research-paper-writing/SKILL.md b/skills/research/research-paper-writing/SKILL.md index f45ce7e2f..a6f343825 100644 --- a/skills/research/research-paper-writing/SKILL.md +++ b/skills/research/research-paper-writing/SKILL.md @@ -22,6 +22,7 @@ End-to-end pipeline for producing publication-ready ML/AI research papers target This is **not a linear pipeline** — it is an iterative loop. Results trigger new experiments. Reviews trigger new analysis. The agent must handle these feedback loops. + ``` ┌─────────────────────────────────────────────────────────────┐ │ RESEARCH PAPER PIPELINE │ @@ -41,6 +42,7 @@ This is **not a linear pipeline** — it is an iterative loop. Results trigger n │ │ └─────────────────────────────────────────────────────────────┘ ``` + --- diff --git a/website/docs/developer-guide/architecture.md b/website/docs/developer-guide/architecture.md index 88ad96269..17e883081 100644 --- a/website/docs/developer-guide/architecture.md +++ b/website/docs/developer-guide/architecture.md @@ -35,7 +35,7 @@ This page is the top-level map of Hermes Agent internals. Use it to orient yours │ │ │ │ codex_resp. │ │ 47 tools │ │ │ │ │ │ anthropic │ │ 19 toolsets │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ -└─────────────────────────────────────────────────────────────────────┘ +└─────────┴─────────────────┴─────────────────┴───────────────────────┘ │ │ ▼ ▼ ┌───────────────────┐ ┌──────────────────────┐ diff --git a/website/docs/developer-guide/gateway-internals.md b/website/docs/developer-guide/gateway-internals.md index 3f9a46bec..b3c4324cc 100644 --- a/website/docs/developer-guide/gateway-internals.md +++ b/website/docs/developer-guide/gateway-internals.md @@ -46,7 +46,7 @@ The messaging gateway is the long-running process that connects Hermes to 14+ ex │ ▼ │ │ SessionStore │ │ (SQLite persistence) │ -└─────────────────────────────────────────────────┘ +└───────┴─────────────┴─────────────┴─────────────┘ ``` ## Message Flow diff --git a/website/docs/reference/skills-catalog.md b/website/docs/reference/skills-catalog.md index 31eb71f11..3d737a168 100644 --- a/website/docs/reference/skills-catalog.md +++ b/website/docs/reference/skills-catalog.md @@ -101,6 +101,7 @@ If a skill is missing from this list but present in the repo, the catalog is reg | [`gif-search`](/docs/user-guide/skills/bundled/media/media-gif-search) | Search and download GIFs from Tenor using curl. No dependencies beyond curl and jq. Useful for finding reaction GIFs, creating visual content, and sending GIFs in chat. | `media/gif-search` | | [`heartmula`](/docs/user-guide/skills/bundled/media/media-heartmula) | Set up and run HeartMuLa, the open-source music generation model family (Suno-like). Generates full songs from lyrics + tags with multilingual support. | `media/heartmula` | | [`songsee`](/docs/user-guide/skills/bundled/media/media-songsee) | Generate spectrograms and audio feature visualizations (mel, chroma, MFCC, tempogram, etc.) from audio files via CLI. Useful for audio analysis, music production debugging, and visual documentation. | `media/songsee` | +| [`spotify`](/docs/user-guide/skills/bundled/media/media-spotify) | Control Spotify — play music, search the catalog, manage playlists and library, inspect devices and playback state. Loads when the user asks to play/pause/queue music, search tracks/albums/artists, manage playlists, or check what's playi... | `media/spotify` | | [`youtube-content`](/docs/user-guide/skills/bundled/media/media-youtube-content) | Fetch YouTube video transcripts and transform them into structured content (chapters, summaries, threads, blog posts). Use when the user shares a YouTube URL or video link, asks to summarize a video, requests a transcript, or wants to ex... | `media/youtube-content` | ## mlops diff --git a/website/docs/user-guide/skills/bundled/media/media-spotify.md b/website/docs/user-guide/skills/bundled/media/media-spotify.md new file mode 100644 index 000000000..4fbda8439 --- /dev/null +++ b/website/docs/user-guide/skills/bundled/media/media-spotify.md @@ -0,0 +1,150 @@ +--- +title: "Spotify" +sidebar_label: "Spotify" +description: "Control Spotify — play music, search the catalog, manage playlists and library, inspect devices and playback state" +--- + +{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */} + +# Spotify + +Control Spotify — play music, search the catalog, manage playlists and library, inspect devices and playback state. Loads when the user asks to play/pause/queue music, search tracks/albums/artists, manage playlists, or check what's playing. Assumes the Hermes Spotify toolset is enabled and `hermes auth spotify` has been run. + +## Skill metadata + +| | | +|---|---| +| Source | Bundled (installed by default) | +| Path | `skills/media/spotify` | +| Version | `1.0.0` | +| Author | Hermes Agent | +| License | MIT | +| Tags | `spotify`, `music`, `playback`, `playlists`, `media` | +| Related skills | [`gif-search`](/docs/user-guide/skills/bundled/media/media-gif-search) | + +## Reference: full SKILL.md + +:::info +The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active. +::: + +# 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": , "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": "", "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. diff --git a/website/docs/user-guide/skills/bundled/mlops/mlops-models-segment-anything.md b/website/docs/user-guide/skills/bundled/mlops/mlops-models-segment-anything.md index ea7d9bc45..7ce304b11 100644 --- a/website/docs/user-guide/skills/bundled/mlops/mlops-models-segment-anything.md +++ b/website/docs/user-guide/skills/bundled/mlops/mlops-models-segment-anything.md @@ -151,6 +151,7 @@ masks = processor.image_processor.post_process_masks( ### Model architecture + ``` SAM Architecture: ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ @@ -161,6 +162,7 @@ SAM Architecture: Image Embeddings Prompt Embeddings Masks + IoU (computed once) (per prompt) predictions ``` + ### Model variants diff --git a/website/docs/user-guide/skills/bundled/mlops/mlops-training-unsloth.md b/website/docs/user-guide/skills/bundled/mlops/mlops-training-unsloth.md index 5ec5713d3..2d936435c 100644 --- a/website/docs/user-guide/skills/bundled/mlops/mlops-training-unsloth.md +++ b/website/docs/user-guide/skills/bundled/mlops/mlops-training-unsloth.md @@ -94,4 +94,4 @@ To refresh this skill with updated documentation: 1. Re-run the scraper with the same configuration 2. The skill will be rebuilt with the latest information -<!-- Trigger re-upload 1763621536 --> + diff --git a/website/docs/user-guide/skills/bundled/research/research-research-paper-writing.md b/website/docs/user-guide/skills/bundled/research/research-research-paper-writing.md index da9329494..790b00d3c 100644 --- a/website/docs/user-guide/skills/bundled/research/research-research-paper-writing.md +++ b/website/docs/user-guide/skills/bundled/research/research-research-paper-writing.md @@ -36,6 +36,7 @@ End-to-end pipeline for producing publication-ready ML/AI research papers target This is **not a linear pipeline** — it is an iterative loop. Results trigger new experiments. Reviews trigger new analysis. The agent must handle these feedback loops. + ``` ┌─────────────────────────────────────────────────────────────┐ │ RESEARCH PAPER PIPELINE │ @@ -55,6 +56,7 @@ This is **not a linear pipeline** — it is an iterative loop. Results trigger n │ │ └─────────────────────────────────────────────────────────────┘ ``` + --- diff --git a/website/scripts/generate-skill-docs.py b/website/scripts/generate-skill-docs.py index 989a1f65d..964632652 100755 --- a/website/scripts/generate-skill-docs.py +++ b/website/scripts/generate-skill-docs.py @@ -120,6 +120,14 @@ def mdx_escape_body(body: str) -> str: elif ch == "}": out.append("}") elif ch == "<": + # Preserve full HTML comments (e.g. ascii-guard ignore markers) — they + # are not HTML tags, so the tag regex below would escape the leading <. + if text[i:].startswith("", i) + if end != -1: + out.append(text[i : end + 3]) + i = end + 3 + continue # Look ahead to see if this is a valid HTML-ish tag. # If it looks like a tag name then alnum/-/_ chars, leave it. # Otherwise escape. diff --git a/website/sidebars.ts b/website/sidebars.ts index 8e593f802..892486873 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -216,6 +216,7 @@ const sidebars: SidebarsConfig = { 'user-guide/skills/bundled/media/media-gif-search', 'user-guide/skills/bundled/media/media-heartmula', 'user-guide/skills/bundled/media/media-songsee', + 'user-guide/skills/bundled/media/media-spotify', 'user-guide/skills/bundled/media/media-youtube-content', ], },