An IDM-style download manager for the terminal, written in Rust.
- β‘ Multi-connection HTTP downloads β a file is split into N segments downloaded in
parallel over
Rangerequests, like IDM. - πΎ Resume everywhere β every segment persists to its own
.partfile, so a paused, crashed, or restarted download resumes exactly where it left off. Incomplete downloads are picked up automatically on the next launch. - π§² Torrents & magnet links β powered by
librqbit(pure-Rust: DHT, uTP, PEX, fastresume). Torrents survive restarts and resume on their own. - π₯οΈ Interactive TUI β a full-screen dashboard with live progress bars, speeds, ETAs, peer counts, pause/resume/delete, and an input bar for pasting URLs or magnets.
- π Browser trigger β while rstdl is running it exposes a small local HTTP API; a bundled Chrome extension (Manifest V3) intercepts downloads and hands them to rstdl.
Prebuilt binaries are attached to every GitHub release β no Rust toolchain needed. Linux (x86_64 static musl + arm64), macOS (universal binary for Intel & Apple Silicon) and Windows (x86_64).
Linux & macOS (one line):
curl -fsSL https://raw.githubusercontent.com/snap-star/rstdl/master/scripts/install.sh | shWindows (PowerShell, one line):
irm https://raw.githubusercontent.com/snap-star/rstdl/master/scripts/install.ps1 | iexThe installers detect your OS/arch, verify the sha256 checksum, drop the
binary in ~/.local/bin (or %LOCALAPPDATA%\rstdl\bin on Windows) and copy
the Chrome extension next to it. Set RSTDL_REPO=you/rstdl to point at a
fork. Downloads go to ~/Downloads by default (see Configuration).
From source (needs Rust):
cargo install rstdl --locked # or: cargo binstall rstdlLinux builds are fully static (
musl), so a single binary runs on any distro. The release workflow builds them with a bundled OpenSSL (--features vendored-openssl).
rstdl # start the TUI dashboard (default)Press a, paste a URL or magnet link, press Enter. That's it.
Headless mode (e.g. on a server, or when you only want the browser trigger):
rstdl serverstdl interactive TUI dashboard (default)
rstdl serve [-p PORT] [-o DIR] headless daemon with the browser-trigger API
rstdl add <url> [-p PORT] add a URL/magnet/.torrent to a running rstdl
rstdl list [-p PORT] list items from a running rstdl
rstdl config print config & state paths
| Key | Action |
|---|---|
a |
add a URL / magnet / .torrent (paste into the input bar) |
p / Enter |
pause / resume the selected item |
d |
delete the selected item (and its files) |
u |
toggle size/speed units (KiB/s β KB/s) β saved to the config |
t |
cycle theme preset (dark β solarized β light) β saved to the config |
r |
reload the theme + units from config.toml (no restart) |
β/β or j/k |
move the selection |
g / G |
jump to top / bottom |
q / Esc |
quit (downloads stay resumable) |
rstdl listens on http://127.0.0.1:4578 (configurable) while running β in both the TUI
and serve modes. The bundled Chrome extension talks to it.
- Build & start rstdl (
rstdlorrstdl serve). - Open
chrome://extensions, enable Developer mode, click Load unpacked, and select theextension/directory. - Click the rstdl toolbar icon to check the connection and tweak what gets intercepted.
The extension:
- Intercepts downloads via
chrome.downloads: if the URL matches the file-type list (or when set to βeverythingβ), it cancels the browser download and POSTs it to rstdl. If rstdl isn't running, the browser download simply proceeds as normal. - Magnet links and
.torrentURLs are always routed to the torrent engine. - Adds a right-click menu: βDownload with rstdlβ on links/selection/page.
- Shows browser notifications when downloads complete, fail, or turn out corrupt β the popup has a toggle for it.
You can also trigger downloads without the extension:
curl -X POST http://127.0.0.1:4578/download \
-H 'Content-Type: application/json' \
-d '{"url": "https://example.com/file.zip", "referer": "https://example.com/"}'
curl -X POST http://127.0.0.1:4578/torrent \
-H 'Content-Type: application/json' \
-d '{"url": "magnet:?xt=urn:btih:..."}'API endpoints: POST /download, POST /torrent (JSON {url} or raw .torrent bytes),
GET /status, GET /items, GET /events?since=N (notification history).
First run writes ~/.config/rstdl/config.toml:
download_dir = "~/Downloads" # default output folder
connections = 4 # parallel connections per HTTP download
max_concurrent_http = 3 # simultaneous HTTP downloads
browser_port = 4578 # browser-trigger API port
min_segment_size = 1048576 # files below this use a single connection
retries = 5 # per-segment retries on transient errors
units = "binary" # "binary" = KiB/s/MiB, "decimal" = KB/s/MBUnits. Sizes and speeds are shown in 1024-based units (KiB, MiB, GiB)
by default. Set units = "decimal" for 1000-based units (KB, MB, GB), or
press u in the TUI to toggle live β the choice is saved to the config.
rstdl can notify you when downloads complete, fail, or turn out
corrupt (e.g. a size mismatch detected at merge). Three channels, all
toggleable under [notifications]:
[notifications]
enabled = true # master switch
tui_banner = true # toast strip at the top of the TUI
desktop = true # real desktop notification (D-Bus / Notification Center)
bell = false # ring the terminal bell on a new TUI toast
command = "" # optional shell hook, e.g. "notify-send 'rstdl' '%t: %b'"
completed = true # notify on success
failed = true # notify on failure
corrupt = true # notify when a download is detected as corruptFor the command hook, %k is replaced with the kind (Completed / Failed /
Corrupt), %t with the title and %b with the body β values are
shell-escaped for you. If your desktop lacks a notification daemon, desktop
notifications are skipped quietly; the TUI banner and event log still work.
bell = true rings the terminal bell each time a banner toast appears (it
only applies while a toast is visible, so it does nothing when
tui_banner = false).
Every notification is also recorded in a sequence-numbered history served at
GET /events?since=N (returns { seq, events: [{ seq, kind, title, body }] })
β the Chrome extension polls it and raises a browser notification for each
new event (toggleable in the extension popup, browserNotify).
Logs: ~/.local/state/rstdl/rstdl.log. Torrent state: ~/.local/state/rstdl/torrents/.
All TUI colors come from the [theme] table in config.toml. Values are
named colors (red, dark_gray, light_blue, ...) or hex RGB (#5fd7ff).
Only include the keys you want to change β everything else uses its default:
[theme]
preset = "dark" # base palette: dark | solarized | light
# (extra per-key colors override the preset)
events = "red" # event-log lines (the "events" panel)
title = "light_green" # header title
border = "blue" # panel borders
dim = "dark_gray" # sizes, ETA, counts
server = "green" # browser-trigger URL
speed = "cyan" # download/upload speeds
kind_http = "cyan" # β icon
kind_torrent = "magenta" # β icon
name = "white" # item names
progress = "yellow" # progress bar while downloading
progress_done = "green" # progress bar at 100%
percent = "light_blue" # percentages
status_done = "green" # Completed / Seeding
status_paused = "yellow" # Paused
status_error = "red" # Failed / Error
status_active = "light_blue" # Downloading / Merging
status_idle = "dark_gray" # other statuses
error = "red" # inline β errors
footer_fg = "black" # footer bar text
footer_bg = "dark_gray" # footer bar background
input_fg = "black" # URL input text
input_bg = "yellow" # URL input backgroundPick a base palette with preset = "dark" | "solarized" | "light". Any color
keys you set are layered on top of the preset, so you can tweak just the events
color without losing the palette.
tβ cycle the preset (darkβsolarizedβlight). The choice is written back intoconfig.toml, so it sticks after a restart.uβ toggle units (KiB/sβKB/s), also persisted toconfig.toml.rβ re-readconfig.tomland apply changes live. Edit the file with your editor, save, pressr: no restart needed. Only the changed line is touched when toggling, so comments and manual edits survive.
rstdl config prints the current paths, the active preset, the unit system,
the resolved theme colors, and the preset list.
Each download lives in a staging folder next to the final file:
<download_dir>/.rstdl/<name>-<hash>/ containing meta.json (URL, headers, size) and one
part.N file per connection. A part file's on-disk size is its resume point, so:
- pause/resume β the next run simply issues
Range: bytes=<partsize>-...per part; - crash/restart β
rstdlscans the staging folder on startup, rebuilds the items, and resumes them automatically; - completion β parts are concatenated into the final file and staging is removed.
Servers without Range support (or files smaller than min_segment_size) fall back to a
single resumable stream.
src/main.rs CLI (clap), logging, runtime wiring
src/manager.rs shared engine state, unified item model, event log
src/httpdl.rs segmented HTTP engine (probe β Range segments β merge), resume, retry
src/torrents.rs librqbit session wrapper (magnet/.torrent, pause/resume/remove, stats)
src/server.rs localhost HTTP API for the browser trigger (axum, CORS)
src/tui.rs ratatui dashboard
src/config.rs XDG config & state paths, theme-preset persistence
src/theme.rs theme model: presets (dark/solarized/light) + color parsing
src/util.rs formatting helpers
extension/ Chrome MV3 extension (intercepts downloads β local API)
scripts/smoke.sh end-to-end test (multi-conn, crash + resume, integrity)
scripts/install.sh one-line installer for Linux & macOS (curl | sh)
scripts/install.ps1 one-line installer for Windows (irm | iex)
.github/workflows/ release.yml β builds + packages binaries for all platforms
bash scripts/smoke.sh # throttled local server β segmented download β kill β resume β sha256 check- Watch folder for
.torrentfiles / URL files (.url,.rdm) - Speed limits per download / global
- Queue priorities and schedules
- Firefox port of the extension
- Media/video stream interception (XDM-style content script)
- Notifications on completion