Quickstart¶
The short version: install, register your music once, hand your agent the skill, and ask for playlists. The first request analyzes your collection (roughly an hour per 10,000 songs, once); everything after that takes seconds, because sonagram only re-reads what changed.
sonagram is config-driven: register your music folders once
(sonagram sources add) and every command knows where to look — the registry,
the central graph, and your saved playlists all live under ~/.sonagram/
(sonagram config shows the resolved paths). Per-library analysis caches live
next to the music itself, under <library_root>/.sonagram/. Explicit-path
command forms also exist for scripting a single folder without any registration.
Install¶
Prebuilt wheels cover macOS (Apple Silicon + Intel), Linux x86_64, and Windows x86_64 on Python ≥ 3.9 — no toolchain needed. Other platforms build from the sdist, which needs a Rust toolchain (rustup):
pip install sonagram # prebuilt wheel on common platforms
export LASTFM_API_KEY=... # optional: enables `enrich`
The wheel’s console script and the standalone binary run the same pure-Rust
code, so pip install sonagram gives the same sonagram command either way.
For AI agents (the fastest path)¶
If you are an AI agent a user pointed at their music library, you don’t touch the flow below by hand — install the bundled skill and follow it:
pip install sonagram
sonagram skill install # writes ~/.claude/skills/sonagram-playlist/SKILL.md
Then read and follow the installed SKILL.md now, in this session (a skill
only auto-loads next session). It walks library detection, scanning, the optional
Last.fm setup, graph build, and playlist delivery.
Config-driven flow (register once, no path args)¶
Register your music folders once and every command fans out over them into one
central graph + playlist store — no <library_root> argument:
sonagram sources add ~/Music # register a folder (repeatable); ~/.sonagram/config.json
sonagram scan # scan every source
sonagram build # merge all sources → the central ~/.sonagram/music.kgl
sonagram status # freshness of every source + graph (graph_stale)
sonagram playlist --ids h1,h2,h3 --name "Deep Focus" --description "a calm work set"
sonagram playlists # list stored playlists
sonagram config shows the resolved graph + playlist-store paths. The explicit
<library_root>-per-command forms below still work for scripting a single
library.
The end-to-end flow¶
sonagram scan ~/Music # walk, hash, analyze → .sonagram/ cache
sonagram enrich ~/Music # optional: fold in Last.fm metadata
sonagram build ~/Music music.kgl # cached analysis → queryable .kgl graph
kglite-mcp-server --graph music.kgl # serve the graph to an AI agent over MCP
Each step is incremental and read-only where it can be: scan re-analyzes
only changed files, and enrich skips already-fetched entities. See the
CLI reference for every flag.
scanwalks~/Musicfor MP3s, content-hashes each file, and runs sonara analysis on anything unseen — writing per-track records under~/Music/.sonagram/. Re-running only analyzes changed files.enrich(optional) fetches Last.fm metadata (popularity, folksonomy tags, MBIDs, similar artists/tracks, original-album mapping) into~/Music/.sonagram/lastfm/. NeedsLASTFM_API_KEY(env or a.envfile).buildmaps the cached records into a kglite.kglgraph, auto-folding in the Last.fm cache when present.serve the
.kglto an AI agent withkglite-mcp-server --graph music.kgl. The agent explores the graph withgraph_overviewandcypher_query— see the Agent guide.
Freshness probe for automation¶
sonagram status <library_root> is a read-only probe (it mutates nothing)
that a skill or CI step can chain before deciding whether to rescan:
sonagram status ~/Music --format json # one stable JSON object
# exit code: 0 = fresh, 1 = needs scan, 2 = no cache
It compares the files on disk against the .sonagram/ cache — counting fresh /
stale / newly-added / deleted tracks and checking each record against the
current sonara analysis schema — without hashing a file or running analysis.
Chain it as status → scan/build if needed → query via MCP.
Materialize a playlist¶
Once an agent (or you) has a query that answers which tracks, in what order,
turn it into a playable .m3u8. Track order is preserved verbatim, never
re-sorted:
# From a Cypher query — write an absolute-path .m3u8:
sonagram playlist ~/Music music.kgl \
--cypher 'MATCH (t:Track) WHERE t.bpm > 120 RETURN t.content_hash ORDER BY t.energy' \
--out set.m3u8
# --copy-to writes a SELF-CONTAINED PORTABLE folder: tracks copied as
# 'NN - Artist - Title.<ext>' next to a relative-path .m3u8. Copies only —
# source files are never moved, retagged, or modified.
sonagram playlist ~/Music music.kgl \
--ids h1,h2,h3 --copy-to ~/Desktop/roadtrip
Pass --out, --copy-to, or both — at least one is required. The copy-folder’s
.m3u8 is named after --out’s file stem when given, else the destination
folder’s own name. See the CLI reference for the
full contract and the Python API for the
programmatic equivalent.
Claude Code skill¶
skills/sonagram-playlist/ in the repo ships an invocable skill for Claude
Code: copy it to ~/.claude/skills/ (filling in your library path), and “make
me a work playlist” becomes a one-liner — the skill chains status →
scan/build (only when stale) → graph curation per the
Agent guide → .m3u8 export.