Headphones
Headphones music-download automation API for swamp — manage artists, albums, and the wanted queue on a Headphones instance over its JSON API.
Model:
@magistr/headphones— artist CRUD (find/add/get/pause/resume/refresh/del), album operations (find/add/get/queue/unqueue), list views (wanted, snatched, upcoming, history), and system actions (force-search, force-process, version/update checks, logs, restart, update).
Bulk onboarding:
set-extrasenables extra release types (EPs, singles, live, compilation, demo, …) for an artist via the web UI's getExtras form — the only surface Headphones exposes for it — then re-imports the discography from MusicBrainz.onboard-artistsis a single idempotent fan-out: for each MusicBrainz artist ID it adds the artist, enables extras, waits for the import to settle, and queues every not-yet-active release, leaving Wanted/Snatched/Downloaded ones untouched.
Includes an optional SSH-backed audit-library method that cross-checks the
Headphones database (Status=Downloaded) against the music filesystem to report
albums marked downloaded but absent from disk.
Resources
2026.08.01.1
Fixes two HIGH latent bugs characterized (pinned, not fixed) by the wave-2b test
backfill below, tracked and resolved via the local headphones-apikey-hardening
issue-lifecycle model:
- Credential leak (apikey-in-URL-query through an unwrapped fetch
rejection):
api()andwebUi()calledfetch()with no surrounding try/catch, so a network-layer rejection (DNS/TLS/reset, or theAbortSignal.timeout) threw a Deno error embedding the full request URL — which carries?apikey=<KEY>on 27/29 methods — verbatim into the thrown message. A hostile/misconfigured server echoing the request URL in its error body leaked the same way through the!response.okpath. Added a pureredactSecrets(message)mapper (stripsapikey=/api_key=<value>, case-insensitive, up to the next&/whitespace/quote/paren, replacing only the value withREDACTED) and wrapped bothapi()'s andwebUi()'sfetch()calls in try/catch via a sharedrethrowRedacted()helper, rethrowingredactSecrets(message)with diagnostics preserved through a REDACTED single-levelcause— never the raw original error — so the secret cannot resurface via a default Error/cause-chain inspection (e.g. Deno's own console formatting, which recurses into.cause) either; routed both!response.okthrow messages through the same redactor. Strict no-op on messages withoutapikey=/api_key=, so the existing429 - quota exceededbehavior is unchanged. - get-artist/get-album array non-unwrap:
get-artistwroteartist: data.artist || datawith no array unwrap, but the real Headphones wire returnsartistas a single-element array (confirmed againstrembo10/headphones'sapi.py_dic_from_query) — the same shapeonboard-artistsalready unwraps viaArray.isArray(data.artist) ? data.artist[0] : data.artist. Applied the identical unwrap toget-artist(preserving the|| datawhole-envelope fallback), and toget-album'salbum: data.album || data(same defect class, same_dic_from_querymechanism, same fix — a free-rider fixed alongside the two named HIGHs). - Bumped
manifest.yamlandmodel.versionto CalVer2026.08.01.1. - Flipped the corresponding characterization pins across
headphones_adversarial_test.ts(the two HONEST-GAP sentinel-propagation pins and the RESIDUAL reflected-body pin — all three now assert redaction, and the two sentinel-propagation tests walk the full.causechain via afullErrorChainText()helper to prove the redacted apikey never resurfaces throughcause, not just.message),headphones_test.ts,headphones_methods_test.ts, andheadphones_coverage_test.ts(the get-artist/get-album unwrap pins) to assert the corrected behavior. The GREEN429 - quota exceededpin and theartist/album-key-ABSENT whole-envelope-fallback pins stay green unchanged, as does the property suite (including the apiKey-sentinel-never-leaks invariant). - Out of scope, deferred (unchanged by this fix): the
unqueue-album→onboard-artistsSkipped-requeue clobber,audit-library's unvalidatedmaxDepth/musicDir/dbPathtrusted-config-boundary gaps, andapiKey's missing.meta({ sensitive: true })annotation — all still tracked by the localheadphones-apikey-hardeningissue-lifecycle model.
Modified 1 models
Release 2026.07.16.2 — align model versions with manifests
Maintenance release across the @magistr extensions. For most packages this
carries no functional change: the only edit is the model's version: field,
brought back in line with its manifest version so the published model type
version and the package version no longer drift.
Functional changes in this release are limited to:
anime-cron: normalizeTitle now strips a ": subtitle" suffix and a trailing parenthesized year before comparison, fixing dedup false-misses where the torrent title carries a subtitle or year that the AniList romaji does not.
arckit: first publish. Standalone ArcKit port — a 12-phase architecture governance state machine with 65 bundled templates, driven by a bundled skill.
Also tracks three extensions (kaiten, observability-agent, music-library) that previously existed only as untracked working-tree directories, recovered from stashes.
Added 1, removed 1 models
Merge pull request #5 from umag/extensions/jscad-stl-pair
extensions: add 15 more @magistr extensions + auto-discover CI
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License declared1/1earned
- Verified public repository2/2earned