yank: one command to download from anywhere
Jun 6, 2026 · 4m read
Every environment has its own cursed download incantation. wget for plain HTTP, gdown for Drive, git clone for repos, yt-dlp for media, aria2c for torrents, rclone for cloud storage. You can never just yank <url> and have the right thing happen.
So I built exactly that.
yank https://example.com/big.iso # parallel HTTP download, resumable
yank https://github.com/cli/cli # git clone --depth 1
yank https://youtu.be/dQw4w9WgXcQ # yt-dlp
yank 'magnet:?xt=urn:btih:...' # aria2c
yank https://drive.google.com/file/d/ID # rcloneThe dispatch architecture
yank is a hybrid: a native engine for HTTP(S) and a dispatch layer for everything else.
Classification runs in order; first match wins:
| Rule | Type | Backend |
|---|---|---|
| magnet: scheme or .torrent path | torrent | aria2c |
| YouTube, Vimeo, Twitch, … | media | yt-dlp |
| Drive, Dropbox, S3, GCS, … | cloud | rclone |
| GitHub, GitLab, *.git, git@… | repo | git |
| ftp:// | ftp | curl |
| http://, https:// direct file | http | native engine |
--backend forces a route, bypassing classification. --dry-run prints the classification and chosen backend without downloading anything — essential for debugging ambiguous URLs.
The dispatch layer delegates to whatever specialist tool is already on your system. If it's missing, yank doctor shows what you need, and yank install-deps installs it with your detected package manager (apt/dnf/pacman/brew), asking first or accepting --yes to skip the prompt.
The native HTTP(S) engine
The heart of yank is a dependency-free Go downloader — zero external tools needed for the common case.
The engine issues a HEAD request to learn Content-Length, Accept-Ranges, ETag, and Content-Disposition. Then:
- Parallel chunked download — if
Accept-Rangesis supported and the file exceeds 1 MiB, split into N ranges (default-x 8) fetched concurrently over separate HTTP connections. - Resume — the in-progress file is written to
<name>.partalongside a sidecar<name>.yank-state.jsonrecording the URL, ETag/Last-Modified validator, and total size. On re-run, the engine validates the ETag, then issues aRange: bytes=<have>-request to continue from where it stopped. If the validator changed (the file was replaced server-side), it restarts cleanly. - Retries — per-chunk, with exponential backoff and jitter (default 5 retries). 5xx and 429 responses are retried;
Retry-Afteris honoured. - Checksums —
--sha256 <hex>(or--checksum algo:hex) verifies the download before the atomic.part → finalrename. Exit code 4 on mismatch.
# verify against a known hash
yank https://example.com/app.tar.gz --sha256 2cf24dba5fb0a30e26e83b2ac5b9e29e...
# script-friendly JSON progress events
yank --json https://example.com/big.iso | jq -c .
# show classification + command, download nothing
yank --dry-run https://youtu.be/dQw4w9WgXcQThe Backend interface
Classification lives in the classify package (pure functions, fully table-tested). Each dispatch backend implements a small interface:
type Backend interface {
Name() string
Tool() string
Build(req Request) (argv []string, err error)
}Build returns an argv slice rather than an *exec.Cmd, so backend command construction can be asserted in unit tests without running anything. A Runner abstraction (LookPath + Run) handles tool detection and execution; backends stream their own stdout/stderr for progress.
Release engineering
yank ships as a single static Go binary (CGO_ENABLED=0) for Linux and macOS across amd64 and arm64. GoReleaser drives the release: 7 assets per release — 4 tar.gz archives + 2 .deb packages + checksums.txt. (A Homebrew tap and AUR package are planned once the tap repo and AUR key exist.)
v0.1.1 (the current release) fixed two install-deps bugs found immediately post-launch: the aria2c package name (aria2 on apt, not aria2c) and missing --yes/--noconfirm flags for non-interactive installation.
Install:
curl -fsSL https://raw.githubusercontent.com/adityachaudhary99/yank/main/install.sh | shWhat's next
v0.2 targets two things: native Google Drive via a gdown backend (Drive share links currently route to rclone, which can't handle the interstitial) and HEAD-less host fallback (some servers — notably Hetzner speed mirrors — serve byte ranges but reject HEAD, causing yank to error out instead of falling back to a ranged GET).