AppleNugs is a native Mac and iPhone client for nugs.net, built by one person for one subscription. A real queue, gapless segues so a live set doesn't break at the seams, offline shows on the phone, lossless where nugs offers it, and five front panels borrowed from the gear this music was recorded on. It plays what your subscription already gives you, and nothing else.
Download AppleNugs 1.4 for macOS — 4 MB, signed and notarized, opens with no Gatekeeper warning, and updates itself from then on. Requires macOS 14 or later. All releases.
The theme changes the palette, the type, the transport, and what the app says to you when nothing is playing. Pick one from the account menu. The idle lines below are the app's, not the README's.
| Panel | What it is | With nothing playing |
|---|---|---|
| Tape Room | Amber on brown, write-on cassette labels, a teal badge for lossless | "Nothing playing. Press / to search." |
| Shoebox | Warm brown, rust play-state, a cassette J-card strip | "B-side's empty. Press / to find a show." |
| The Receiver | Brushed metal and a real VU meter, tube-teal play-state, knurled buttons | "No signal. Press / to tune in." |
| Click Wheel | Monochrome pocket player; the cover art is the only pigment on screen | "Nothing playing. Give the wheel a spin." |
| Soundboard | Near-black, and the cover's own colour washes the chrome | "Nothing playing — press / to search." |
The Receiver renames the dashboard's columns to TUNED TO, SIGNAL and REELS, because at that
point you may as well commit.
Playback. The next track is resolved and parked in an AVQueuePlayer while the current one
is still playing, so a segue lands where the band put it. Format preference runs ALAC → FLAC →
MQA → AAC → HLS, with automatic fallthrough if one fails. A real queue you can reorder and clear.
The quality readout is measured, not guessed. Format, platform tier, sample rate, bit depth, channels and buffer-ahead come from the decoder. If the dashboard says ALAC 24/96, it is ALAC 24/96.
Library. An artist opens on one scope at a time — Shows, Videos or Albums, each with its
count — over a filter that takes a venue, a city or a date, as a flat reverse-chronological list
under sticky month headers. Filtering, grouping and sorting are derived state rebuilt only when
the inputs change, not recomputed in body, so theme switches and playback ticks don't re-sort
the catalog. Search, follow artists, save shows and videos.
Video. Continue Watching with resume positions, Live & Upcoming, Recent Exclusives for just-ended livestream replays, a paged on-demand grid, chapters, a quality cap, and a full-screen toggle on the Mac. Audio and video share one playback arbiter, so they never talk over each other.
Sign-in, your way. Browser SSO — Apple, Google, Facebook, SiriusXM — through
ASWebAuthenticationSession with OAuth2 Authorization Code and PKCE, or plain email and
password. Tokens live in the Keychain and refresh about a minute before they expire.
System integration. Media keys, Control Center, AirPods and lock-screen transport via
MPRemoteCommandCenter; cover art in the system widget; AirPlay from the now-playing screens.
A catalog of live music is not an album grid. Goose has 484 shows in here, and they differ by date and venue rather than by cover art, so the page is a filter over dense one-line rows under sticky month headers, newest first.
It used to be a three-level Category → Year → row tree. That cost two clicks before a single show was visible, buried what's recent three levels down, and then dumped 84 undifferentiated rows the moment you opened a year. This is the replacement.
The iPhone app shares the same core: a five-tab shell, background audio with lock-screen transport, themed full-screen now-playing, and a now-playing pill that rides above every pushed screen with a progress ring drawn around the artwork. Portrait-locked, with rotatable full-screen video and automatic Picture-in-Picture.
Download whole shows in the best lossless format offered and they play with no network, gapless included; the player prefers a local file whenever it has one. Launch in airplane mode and it offers to listen offline directly.
The iPhone app is personal-install only — build it yourself and run it on your own device. There is no App Store build and no TestFlight.
Important
AppleNugs is an independent client. It is not affiliated with, authorized, sponsored, or endorsed by nugs.net. It signs in with your own account and plays exactly what your subscription already entitles you to play. Shows saved for offline listening stay inside the app's own container on your own device, which is what the official apps do too. It redistributes nothing and circumvents no DRM. You are responsible for complying with the nugs.net Terms of Service. See NOTICE.md.
Download the signed DMG, drag it to Applications, and let Sparkle handle updates from there. "Check for Updates…" lives under the app menu.
To build it yourself you need Xcode 26.1 or later — the iOS target's deployment floor is iOS
26.1, for one tabViewBottomAccessory overload — and
XcodeGen (brew install xcodegen). The .xcodeproj is
generated, not committed:
xcodegen generate
open AppleNugs.xcodeproj # then ⌘RSign in with browser SSO or your nugs.net email and password. Tokens are stored in the
Keychain, with a chmod 600 file fallback inside the app's own container for unsigned and
ad-hoc builds that have no Keychain entitlement.
| Key | Action |
|---|---|
| / | Focus search |
| space | Play / pause |
| n / p | Next / previous track |
| ← / → | Seek −10s / +10s (⇧ for ∓30s) |
| 0 | Seek to start |
| 1–9 | Seek to 10%–90% of the track |
| Esc | Blur a focused input |
| ⌃⌘→ / ⌃⌘← | Next / previous (menu) |
| ⌘⇧F | Focus search (menu) |
| ⌥⌘I | Toggle the dashboard panel |
Plain-letter keys go through a window-level event monitor and pass through untouched while a text field has focus. On the iPhone, transport lives on screen and on the lock screen instead.
Build it yourself — schemes, the iOS target, tests
Everything under AppleNugs/ is shared by both apps except AppleNugs/macOS/ (Sparkle glue,
keyboard monitor, split-view shell, desktop faceplate) and AppleNugs/iOS/ (app entry, tab
shell, now-playing screens, the pill, orientation gate). project.yml excludes each platform
directory from the other target; small in-file divergences use #if os(...).
Schemes are AppleNugs (macOS), AppleNugs-iOS, and AppleNugsTests. The project must stay
warning-free under SWIFT_STRICT_CONCURRENCY=complete on both app schemes.
xcodebuild -project AppleNugs.xcodeproj -scheme AppleNugs -configuration Debug buildThe committed configuration is ad-hoc signed so it builds in CI and on any machine with no Apple Developer team. Producing a signed, notarized build is covered in DISTRIBUTION.md.
iOS, personal install. A team selected in Xcode is wiped by the next xcodegen generate. To
make it stick, export your team ID first:
export APPLENUGS_TEAM_ID=YOUR_TEAM_ID # then: xcodegen generateor headless against the simulator:
xcodebuild -project AppleNugs.xcodeproj -scheme AppleNugs-iOS \
-configuration Debug -destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO buildSparkle is macOS-only; update an iOS install by rebuilding. After the first install,
xcodebuild against the device destination plus xcrun devicectl device install app does the
whole cycle without a cable.
Tests. 98 pure-logic tests in a host-free bundle — no app launch, no @testable:
xcodebuild test -project AppleNugs.xcodeproj -scheme AppleNugsTestsWhy native, and not a web app
Two nugs.net platform constraints rule out a browser-based client: CORS, and the audio CDN's
required Referer and User-Agent headers, which browsers will not let JavaScript set. A native
app has neither problem — AVURLAsset carries the headers directly, so there is no proxy tier to
run and nothing of yours passes through a machine that isn't yours:
┌──────────────────────────────┐ TLS ┌──────────┐
│ AppleNugs (macOS / iOS) │ ────────► │ nugs.net │
│ SwiftUI · AVFoundation │ └──────────┘
│ tokens in the Keychain │
└──────────────────────────────┘
Notes for hacking — the things that cost a weekend to learn
platformIDis a device tier, not a format. The stream endpoint takes what looks like a format selector and isn't one. The app asks{1, 4, 7, 10}concurrently and works out what it actually got from the URL path (.flac16/,.alac16/,.m3u8, …).- The catalog JSON is inconsistent about casing (
artistIDvsArtistID) and pluralization. All shape-dependent digging is quarantined inCore/JSON.swiftandCore/Catalog.swiftso it can't leak into view code. - Catalog dates are UTC-midnight instants. Reading them with
Calendar.currentshifts a show by a month, or by a year on 1 January. UseCrateSection.catalogCalendar. - The OAuth callback scheme is deliberately not in
Info.plist. Browser login relies on id.nugs.net trusting the mobile client'sclient_idandnugsnet://oauth2/callbackpair, andASWebAuthenticationSessioncaptures the callback in-process, so registering the scheme would only invite other apps to claim it. - Offline downloads live in
Application Support/AppleNugs/Downloads/<containerID>/behind amanifest.jsonindex, excluded from backups. Only direct-file picks are downloadable, never HLS. - The UI-test harness launches with
-UITESTfor a stubbed logged-in state with no network and no Keychain.-UITestSeedQueueparks a fake queue so transport UI renders,-UITestTab artists|search|favorites|videospicks the starting tab,-UITestTheme <ThemeID>forces a theme, and-UITestShowNowPlayingopens the full-screen player on iOS. - The banner at the top of this file is generated, not drawn by hand:
swift scripts/generate-banner.swift. The lettering is outlined to paths because GitHub serves repository files underdefault-src 'none', which blocks every font an SVG might try to fetch — including a base64 one. A<text>element would quietly fall back to a system font. - The unofficial API surface is also documented by Sorrow446/Nugs-Downloader and Dniel97/orpheusdl-nugs. Check those when an endpoint or a shape stops working.
No shuffle and no repeat. There are three partial transport-control implementations in the codebase already, and they want collapsing into one before either of those gets written.
No CONTRIBUTING, no code of conduct, no issue templates, no security policy. This is one person's music player, and the paperwork would be set dressing. If something is broken, open an issue and say what it did.
MIT for the AppleNugs source. It covers this code only and grants no rights in nugs.net's service, content, or marks — see NOTICE.md.



