How we built a small Pexels CLI (and the aarch64 cross-build trap we escaped)
A tiny Rust CLI that speaks the Pexels API, and the practical fix for aarch64 cross-builds on GitHub Actions.

The spark
This started with a simple ask: Can we make it easy for agents (and humans) to find context‑matching photos and immediately do something with them? We love GitHub’s gh because it feels natural in a pipeline. We wanted that feeling: small commands, predictable output, friction‑free follow‑ups.
What we built

- A tiny Rust CLI that speaks the Pexels API.
- YAML by default (JSON optional), with a consistent envelope { data, meta } so tools like yq can parse it without surprises.
- Light defaults and field projection (ids/urls/files/thumbnails), with photo IDs included by default so you can chain commands.
- Everyday flows: search → get URL → download.
Why Rust and why Pexels
- Rust: one fast, safe binary for Linux/macOS/Windows, great tooling (cargo, clippy), straightforward CI.
- Pexels: license‑friendly, clean API, maps well to CLI tasks.
The interesting part: linux‑arm64 (aarch64) release broke in CI
On GitHub Actions’ default Ubuntu runners, we shipped multi‑arch artifacts. amd64 was fine; macOS and Windows were fine. aarch64‑unknown‑linux‑gnu failed with an openssl‑sys error. Two lines in the log told the story:
- “Could not find directory of OpenSSL installation.”
- “pkg‑config has not been configured to support cross‑compilation.” And the reminder: $HOST was x86_64‑unknown‑linux‑gnu, $TARGET was aarch64‑unknown‑linux‑gnu. In other words, we were building for arm64 on a host that only knew how to find x86_64 libraries.
What we tried (and why it didn’t work)
- Vendor/build OpenSSL in CI: heavy, slow, fragile for a small CLI; adds maintenance we didn’t want.
- Set OPENSSL_DIR=/usr and hope: pkg‑config kept resolving host (x86_64) libs, not target (aarch64).
- “Just use cross”: helpful, but the default runner still lacked the target’s headers/libs, and pkg‑config stayed host‑oriented.
What actually fixed it (the minimal playbook)
We treated it like a real cross‑compile:
- Give the runner the target’s dev packages so aarch64 has real headers/libs (e.g., OpenSSL and zlib for arm64) and install the target‑aware pkg‑config wrapper (aarch64‑linux‑gnu‑pkg‑config).
- Make the toolchain explicit for the target: use the aarch64 linker/compiler/archiver; point pkg‑config to the aarch64 wrapper; and tell openssl‑sys where to look (the target include/lib paths under /usr/include/aarch64‑linux‑gnu and /usr/lib/aarch64‑linux‑gnu).
- Build for aarch64, then verify the artifact’s architecture before packaging.
Why it worked
openssl‑sys relies on pkg‑config and a few environment hints to find headers/libs. As soon as the runner had the target’s dev packages and pkg‑config was target‑aware, resolution flipped from host to target and the build went through—no vendored OpenSSL needed.
After that fix, we shipped v0.1.0 of the CLI. Here’s how to use it
# authenticate (or set PEXELS_TOKEN)
pexels auth login --token "$PEXELS_TOKEN"
pexels auth status
# search and chain by id
pexels photos search -q "cats"
ID=$(pexels photos search -q "cats" | yq -r '.data[0].id')
# fetch a canonical URL
pexels photos url "$ID"
# download the original
pexels photos download "$ID" ./cat.jpg
What’s next
We’ll publish to package managers (Homebrew/Scoop). For now, binaries are available on GitHub: https://github.com/agynio/pexels-cli