# 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](https://github.com/rivo/tview) (on top of tcell) - **License:** MIT - **Module:** `git.stinnesbeck.com/nils/arr-tui` - **Status:** M3 done ## 1. Goals 1. Search for movies (Radarr) and series (Sonarr) from one place. 2. Add a result to Radarr/Sonarr with sensible defaults (quality profile, root folder, monitored) and trigger a search so it gets downloaded. 3. Show the SABnzbd queue and history with live progress. 4. 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 `id` of `0` when the item is not yet in the library; a non-zero `id` means 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 /movie` or `POST /series` with `addOptions.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` / `1-4` | Switch view | | `F1-F4` / `Alt+1-4` | 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 | | `d` | Delete selected download (with confirm) | | `i` | Interactive release search for the selected item | | `g` | Grab the selected release | | `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). ```json { "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.Server` with 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 (ideally a docker-compose file for local dev). ## 9. Milestones - [x] **M0** — Project skeleton: `go mod init`, config loading, empty tview app with tabs and status bar. - [x] **M1** — Radarr client: system status, lookup, quality profiles, root folders, add movie. - [x] **M2** — Movie search + add flow in the UI. - [x] **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, error handling, README, release builds. - [ ] **M8+** — v0.2 features (library view, series drill-down). ## 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.