Skip to main content

plex-axi

An Agent eXperience Interface (AXI) CLI for a Plex music library.

Structured, per-field music search. Every published Plex CLI, MCP server and LLM wrapper takes a single free-text query string and hands it to the server as one blob — which is why searching for an artist and a song title reliably returns nothing. plex-axi gives each value its own flag and each flag its own Plex field, and lets Plex evaluate the whole predicate server-side.

$ plex-axi search --artist "Example Artist" --track "Example Track"
count: 1 of 1 total
grouped: title
filters[2]{field,operator,value}:
  artist.title,contains,Example Artist
  track.title,contains,Example Track
tracks[1]{key,title,artist,album}:
  111,Example Track,Example Artist,Example Album
item:
  media_id: "plex://<machineIdentifier>/111"
  rating_key: 111
  guid: "plex://track/a1b2c3d4e5f60718293c0111"
  note: "rating_key is local to this server and changes when an item is re-matched or the library is rebuilt; guid is the identifier that survives, so keep them together"

It also exposes the parts of Plex's music surface that nothing else does: the library's own genre, mood and style vocabularies; sonic similarity with the server's own distance; the music-analysis version behind an empty "more like this"; and, behind a flag, whether the server can actually read the file.

It deliberately does not play anything. There is no play command and no concept of a speaker, room, player or client. Every command ends at a labelled media_id, and dispatch belongs to whatever owns the speakers where you are. That is a decision enforced by a test, not a missing feature — see AGENTS.md.

Install

pipx install plex-axi        # or: uv tool install plex-axi

Or run it without installing:

uvx plex-axi search --artist "Example Artist"
pipx run plex-axi genres

Configure

Both values come from the environment. There is no --token flag and no credential file: a token on a command line leaks into shell history and the process table, and a Plex token is a bearer credential for an entire library.

export PLEX_URL=http://plex.example.com:32400   # the server on your local network
export PLEX_TOKEN=<a Plex access token>
export PLEX_SECTION='Example Music'             # only if the server has more than one

Point PLEX_URL at the server itself rather than at plex.tv, so the tool keeps working when plex.tv is unreachable and no invocation pays a cloud round-trip. Finding a token is documented at https://support.plex.tv/articles/204059436.

plex-axi doctor    # exits non-zero when any check fails, so it works as a hook or CI gate

Use

plex-axi                                              # the library at a glance
plex-axi search --artist "Example Artist" --rated-min 4
plex-axi search --genre Jazz --type album --limit 10
plex-axi genres                                       # and `moods`, `styles`
plex-axi track 12345 --check-files
plex-axi similar 12345 --max-distance 0.1
plex-axi recent
plex-axi sessions
plex-axi api /library/sections                        # the escape hatch, read-only

Every command takes --help, which is the authoritative reference for its flags.

Output

TOON on stdout, exit non-zero on failure. --human for a readable table, --json for raw JSON. Errors are structured on stdout too and carry the command that fixes them, so a wrong flag corrects itself in one turn.

Rules of thumb

  • Use a flag per field. --artist X --track Y searches two Plex fields; --query "X Y" searches one string. --query exists for the case where there genuinely is only one unstructured string, and its --help says so.
  • Ratings are stars, 0–5, in both directions. A rating printed in a result can be passed straight back to --rated-min.
  • Genres and styles live on the artist, not the track — that is how Plex tags a music library. plex-axi genres prints the exact strings the server will accept; pass one of those, not a synonym.
  • A zero result is an answer. It names the filters that matched nothing and the command that lists the real vocabulary.
  • rating_key is local to one server and moves when an item is re-matched or the library is rebuilt. Keep the guid beside it anywhere the value is written down.

What media_id is, and what consumes it

Every command that identifies one item prints a block like this and then stops:

item:
  media_id: "plex://a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0/111"
  rating_key: 111
  guid: "plex://track/a1b2c3d4e5f60718293c0111"
  note: "rating_key is local to this server and changes when an item is re-matched or the library is rebuilt; guid is the identifier that survives, so keep them together"

media_id is plex://<machineIdentifier>/<ratingKey>: the id of the item, prefixed by the id of the server it lives on. That combination is what a media player needs to fetch it without being told separately which Plex server you meant, and it is the form most Plex clients and integrations accept as a media content id.

It matters that it is that form. Five plex:// strings circulate and they all look alike. Two of them break a consumer: plex://track/<ratingKey> parses the word track as a server name and resolves to a server that does not exist, and plex://track/<24-hex> — which is a perfectly legitimate Plex identifier, the one printed here as guid — raises an error inside consumers that expect a number in that position. plex-axi emits only the safe form, and a test sweeps every command asserting it never emits the other two.

plex-axi does not dispatch it anywhere. Handing it to something that plays is a single call in whatever already owns your speakers. In Home Assistant, for example, the media_player.play_media service takes it directly:

service: media_player.play_media
target:
  entity_id: media_player.example_speaker
data:
  media_content_type: music
  media_content_id: "plex://a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0/111"

That is illustrative, not a dependency: nothing in plex-axi knows Home Assistant exists, and the same id is what Plex's own clients and other integrations consume. Substitute whatever plays music where you are.

Keep the guid with the rating_key wherever you write one down. A rating key is a row number in one server's database. It changes when an item is re-matched or the library is rebuilt, and the same number will then resolve to a different recording — silently. The guid is the identifier that survives, which is why both are printed together and why the note travels with them.

Agent integration

Install the skill so an agent loads the guidance on demand:

npx skills add dmealing/plex-axi --skill plex-axi

skills/plex-axi/SKILL.md is generated from the CLI's own command table, so it cannot describe a flag that does not exist.

Design notes

  • The client library is python-plexapi, chosen for its model rather than its transport: it is the only Plex library with MusicSection, the filter language, and server-side field validation. It is synchronous, which is a hazard in a long-running service and simply correct in a one-shot CLI — do not "fix" it.
  • Plex's media-query language is documented in the community OpenAPI specification (https://github.com/LukeHagar/plex-api-spec, MIT), which is the thing to cite rather than re-derive.
  • Development notes, including every sharp edge behind the code, are in AGENTS.md.

Contributing

pip install -e ".[dev]"
scripts/install-hooks.sh
pytest && ruff check . && ruff format --check . && scripts/leakcheck.py

This repository is public and a music library is full of identifying content, so a leak guard runs in a pre-commit hook, a commit-msg hook and CI. Its coverage is bounded and it does not replace review: it detects shapes — tokens, addresses, machine identifiers, media paths — and it cannot detect a real artist name, which has no shape. Use obviously-synthetic content everywhere, including tests and fixtures.

Tests never need a live Plex server or a real token, and must not start to.

Licence

MIT. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plex_axi-0.1.0.tar.gz (113.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plex_axi-0.1.0-py3-none-any.whl (68.9 kB view details)

Uploaded Python 3

File details

Details for the file plex_axi-0.1.0.tar.gz.

File metadata

  • Download URL: plex_axi-0.1.0.tar.gz
  • Upload date:
  • Size: 113.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for plex_axi-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1b5ca19f1b684b0ca7d57cc645a67a4f8d672547582d584bdb76e8bb9b6da3b4
MD5 ec38c559359a8a4c623e8422f7745372
BLAKE2b-256 a581a44aa8557815bbe949173fa1894077eb759bc9a2de5d59c41d246faef047

See more details on using hashes here.

Provenance

The following attestation bundles were made for plex_axi-0.1.0.tar.gz:

Publisher: release.yml on dmealing/plex-axi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file plex_axi-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: plex_axi-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 68.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for plex_axi-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d5970a2559f116bf4eb0d5b37713a3028b9adecdec4b028bdf4de6a659ea8852
MD5 d067406bb99ee4b4305562cc44e5fb0d
BLAKE2b-256 eb89ac24f83d1d22cd0595c019760cbe44e249ead0c3356418d0489e9f03b80d

See more details on using hashes here.

Provenance

The following attestation bundles were made for plex_axi-0.1.0-py3-none-any.whl:

Publisher: release.yml on dmealing/plex-axi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.6.1

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page