cal · writing

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   # rclone

The 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-Ranges is 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>.part alongside a sidecar <name>.yank-state.json recording the URL, ETag/Last-Modified validator, and total size. On re-run, the engine validates the ETag, then issues a Range: 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-After is honoured.
  • Checksums--sha256 <hex> (or --checksum algo:hex) verifies the download before the atomic .part → final rename. 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/dQw4w9WgXcQ

The 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 | sh

What'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).

← all writing