12 KiB
arr-tui — Specification
A terminal UI to search for movies and series, add them to Radarr / Sonarr, and watch the downloads progress in SABnzbd.
- Language: Go
- TUI library: tview (on top of tcell)
- License: MIT
- Module:
git.stinnesbeck.com/nils/arr-tui - Status: M8 done
1. Goals
- Search for movies (Radarr) and series (Sonarr) from one place.
- Add a result to Radarr/Sonarr with sensible defaults (quality profile, root folder, monitored) and trigger a search so it gets downloaded.
- Show the SABnzbd queue and history with live progress.
- Be fast and keyboard-driven; usable over SSH.
Non-goals (for now)
- Managing indexers, download clients, or Radarr/Sonarr settings.
- Replacing the Radarr/Sonarr web UIs for library management.
- Torrent clients (SABnzbd / Usenet only).
- Multi-user / auth beyond API keys.
2. How the pieces fit together
┌────────────┐ lookup / add / release search ┌──────────┐
│ │ ────────────────────────────────▶ │ Radarr │──┐
│ arr-tui │ └──────────┘ │ sends NZB
│ │ ────────────────────────────────▶ ┌──────────┐ │
│ │ │ Sonarr │──┤
└─────┬──────┘ └──────────┘ │
│ queue / history / pause / resume ▼
└────────────────────────────────────────▶ ┌──────────────┐
│ SABnzbd │
└──────────────┘
Radarr/Sonarr are expected to already have SABnzbd configured as their download client. arr-tui talks to all three APIs directly; it does not send NZBs to SABnzbd itself, except possibly via the manual-release flow, which still goes through Radarr/Sonarr (see 4.3).
3. External APIs
| Service | API | Auth | Endpoints used |
|---|---|---|---|
| Radarr | v3 REST (JSON) | X-Api-Key header |
GET /api/v3/movie/lookup?term=, GET /api/v3/movie, POST /api/v3/movie, GET /api/v3/qualityprofile, GET /api/v3/rootfolder, GET /api/v3/queue, GET /api/v3/release?movieId=, POST /api/v3/release, POST /api/v3/command (MoviesSearch), GET /api/v3/system/status |
| Sonarr | v3 REST (JSON) | X-Api-Key header |
GET /api/v3/series/lookup?term=, GET /api/v3/series, POST /api/v3/series, GET /api/v3/qualityprofile, GET /api/v3/rootfolder, GET /api/v3/queue, GET /api/v3/release?episodeId= / ?seriesId=&seasonNumber=, POST /api/v3/release, POST /api/v3/command (SeriesSearch, SeasonSearch, EpisodeSearch), GET /api/v3/system/status |
| SABnzbd | /api?mode=...&output=json |
apikey query param |
mode=queue, mode=history, mode=pause / resume, mode=queue&name=delete, mode=version |
Notes:
- Endpoint shapes should be verified against each instance's own Swagger / the official docs when implementing; Radarr and Sonarr occasionally change fields between versions.
- Lookup results include an
idof0when the item is not yet in the library; a non-zeroidmeans it is already added. We use this to show an "in library" badge.
4. Features
4.1 MVP (v0.1)
- Config: load URLs + API keys from a config file; fail with a clear message if missing or unreachable.
- Search: one search box, with a toggle (movies / series). Results list with title, year, rating, status, and an "in library" marker. Detail pane with overview, runtime, genres, network/studio.
- Add: from a result, open a small form: quality profile, root folder, monitored (yes/no), "search now" (yes/no); for series also monitor mode (all / future / first season / none). Submit calls
POST /movieorPOST /serieswithaddOptions.searchForMovie/searchForMissingEpisodes. - Interactive release search: for a movie (or a series' season/episode) in the library, list releases from indexers (
GET /release) with title, indexer, size, quality, age, and rejection reasons; sort/filter; grab a chosen one (POST /release). Rejected releases can be grabbed only after an explicit confirmation. - Downloads view: all downloads and queues in one place, auto-refreshing every few seconds:
- SABnzbd queue (name, size, progress, ETA, speed) and history (completed / failed, with retry).
- Radarr and Sonarr queues (status, warnings, import errors), so stuck imports are visible.
- Global speed / pause state in the status bar. Pause/resume and delete a SABnzbd job.
- Status bar / help: key hints, connection state of each service.
4.2 v0.2
- Library view: browse existing movies/series with filters (missing, downloaded, monitored).
- Series drill-down: seasons and episodes, toggle monitoring per season/episode, trigger season/episode search.
4.3 Later / ideas
- Calendar / upcoming view.
- Notifications on completed downloads.
- Multiple instances per service (e.g. 4K Radarr).
- Poster preview via sixel / kitty graphics where supported.
5. UI Design (tview)
Layout sketch:
┌ arr-tui ───────────────────────────────────────────────────────────┐
│ [Movies] Series Downloads Library │ ← tab bar
├────────────────────────────────┬───────────────────────────────────┤
│ Search: ______________________ │ Title (Year) │
│ │ ★ 7.8 · 142 min · Sci-Fi │
│ > Movie One (2021) ✓ │ │
│ Movie Two (2019) │ Overview text wraps here... │
│ Movie Three (2024) │ │
├────────────────────────────────┴───────────────────────────────────┤
│ ↓ 12.4 MB/s · 2 jobs · Radarr ● Sonarr ● SABnzbd ● ? help │ ← status bar
└────────────────────────────────────────────────────────────────────┘
tview primitives: Pages (views), Flex (layout), InputField (search), List/Table (results, queue), TextView (details), Form (add dialog), Modal (confirmations).
Keybindings (initial proposal):
| Key | Action |
|---|---|
Tab / Shift+Tab |
Switch panel within the current view (never the view) |
F1-F4 |
Switch view, also while typing in a search box |
/ |
Focus search |
Enter |
Search / open details / confirm |
a |
Add selected result |
m / s |
Switch to movies / series search |
p |
Pause/resume SABnzbd (Downloads view) |
d |
Delete selected download, history entry or Radarr/Sonarr queue item (with confirm) |
t |
Retry the selected failed download (history) |
Tab / Shift+Tab |
Downloads view: cycle focus between SABnzbd queue, Radarr/Sonarr queue and history |
b |
Downloads view (Radarr/Sonarr queue): blocklist the release and search for another |
i |
Interactive release search for the selected library item (movies; series ask for a season, then an episode or the whole season) |
g / Enter |
Release list: grab the selected release (rejected ones ask for confirmation) |
s / f |
Release list: cycle sort (best / size / age) / hide rejected |
Esc |
Close a dialog or the release list |
r |
Refresh |
? |
Help |
q / Ctrl+C |
Quit |
6. Architecture
- All network I/O runs in goroutines; UI updates go through
app.QueueUpdateDraw. Never block the UI thread. - Debounced search (e.g. 300 ms) with request cancellation via
context.Context. - A single ticker drives SABnzbd/queue polling, only while the Downloads view (or the status bar) needs it.
- HTTP client per service with timeouts, and a typed error that the UI can show in the status bar rather than crashing.
Proposed layout:
arr-tui/
├── cmd/arr-tui/main.go # entry point, wires config + UI
├── internal/
│ ├── config/ # load/validate config
│ ├── radarr/ # client + types
│ ├── sonarr/ # client + types
│ ├── sabnzbd/ # client + types
│ ├── arr/ # shared bits (HTTP helper, quality profile, root folder types)
│ └── ui/ # tview views: search, add form, downloads, status bar
├── SPECS.md
├── go.mod
└── README.md
Dependencies: github.com/rivo/tview, github.com/gdamore/tcell/v2. Prefer the standard library net/http and encoding/json for the API clients — no generated SDKs.
7. Configuration
Format: JSON. Location: $XDG_CONFIG_HOME/arr-tui/config.json (fallback ~/.config/arr-tui/config.json), overridable with --config. Environment variables override file values (e.g. ARR_TUI_RADARR_APIKEY) so secrets don't have to live in the file. Parsed with the standard library encoding/json (unknown fields rejected to catch typos).
{
"radarr": {
"url": "http://localhost:7878",
"apikey": "...",
"default_quality_profile": "HD-1080p",
"default_root_folder": "/movies"
},
"sonarr": {
"url": "http://localhost:8989",
"apikey": "...",
"default_quality_profile": "HD-1080p",
"default_root_folder": "/tv"
},
"sabnzbd": {
"url": "http://localhost:8080",
"apikey": "..."
},
"ui": {
"refresh_seconds": 3
}
}
The default quality profile / root folder fields are optional.
The file should be created with mode 0600; warn if it is more permissive. API keys are never logged.
8. Testing
- Unit tests for each client using
httptest.Serverwith recorded JSON fixtures. - Config parsing / validation tests.
- UI logic (formatting, state) kept out of tview widgets so it can be tested without a terminal.
- Manual testing against real Radarr/Sonarr/SABnzbd instances (
dev/up.shstarts pinned Radarr/Sonarr/SABnzbd containers, seedev/README.md).
9. Milestones
- M0 — Project skeleton:
go mod init, config loading, empty tview app with tabs and status bar. - M1 — Radarr client: system status, lookup, quality profiles, root folders, add movie.
- M2 — Movie search + add flow in the UI.
- M3 — Sonarr client and series search + add flow.
- M4 — SABnzbd client and Downloads view (queue + history) with live updates and pause/resume/delete.
- M5 — Radarr/Sonarr queues merged into the Downloads view.
- M6 — Interactive release search (movies, then series seasons/episodes) and grab.
- M7 — Polish: help screen (
?/F9), per-service connection indicators, README, Makefile release builds,--version. - M8 — Library view (movies/series, filters, text filter, monitor toggle, automatic search) and series drill-down (seasons/episodes, monitoring, search, release search).
- M9+ — ideas: calendar, notifications, delete from library, multiple instances.
10. Open Questions
Open: none.
Decided:
- Go module path:
git.stinnesbeck.com/nils/arr-tui. - One instance each of Radarr, Sonarr and SABnzbd (multi-instance is a later idea).
- Minimum Go version: latest stable.
- License: MIT (Nils Stinnesbeck). The tool will be published, so keep secrets out of the repo, include a
config.example.json, and write a proper README. - Config format: JSON.
- Interactive release search: in the MVP.
- Downloads view: shows SABnzbd and the Radarr/Sonarr queues from the start.