Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fx

Fast, offline-first currency converter in your terminal. Millisecond conversions, every currency in the feed including thinly traded ones, historical dates, and a 90-day anomaly gauge. Dependency-free Rust binary (the rare network fetch shells out to curl).

$ fx 100 usd cad
139.4703

  90d 1.37478 – 1.42319   │   now 1.3947   │   [████░░░░░░] 41%
  → USD mid-range vs CAD — mid 90d range

Today's rate moves, so the figures above will not match yours. A date pins them:

$ fx eur inr 2025-06-29
100.2144

  2025-06-29  1 EUR = 100.2144 INR

    2025-06-27  Fri   100.1889   -0.3309
    2025-06-28  Sat   100.0629   -0.1260
  › 2025-06-29  Sun   100.2144   +0.1515
    2025-06-30  Mon   100.1875   -0.0269
    2025-07-01  Tue   100.9411   +0.7536

On a terminal the output is gently colored (soft cyan focal value, sage/terracotta signals); piped or with NO_COLOR it's plain text with the identical layout.

Features

  • Snappy — the hot path only reads a local cache file, never the network except on a genuinely cold cache. A warm call measured ~2.8 ms end to end (mean of 50 runs including fork/exec, Linux x86-64, release build); your hardware will differ, so treat it as an order of magnitude, not a promise.
  • Every currency in the feed (339 as of writing) — fiat, metals and crypto, all as first-class citizens. Thinly traded codes are not second-class.
  • Historical rates + trendfx usd cad yesterday, fx 50 usd cad 2025-05-04, fx eur gbp 3w. A date query shows a 5-day window centered on the target (neighbors dimmed, day-over-day deltas colored) so the direction is obvious.
  • 90-day anomaly gauge — shows where today's rate sits in its recent 90-day range, for every currency, with the position marked on a simple bar.
  • Inverse hint — when a result is awkwardly small (< 0.1, as fx 1 inr eur is) fx adds one dim, labeled line with the naturally-scaled reverse rate (↔ 1 EUR = 109.9086 INR). The primary line is always what you asked; the hint appears only when it helps, so normal pairs stay uncluttered.
  • Gentle, TTY-aware color — muted 256-color palette on an interactive terminal; automatically plain when piped or when NO_COLOR / TERM=dumb is set, so the first line stays a bare, parseable number for scripts.
  • Natural aliasesus, uk, euro, yen, swiss, aussie, … or any ISO 4217 code.
  • Robust / offline-first — stale cache still answers instantly and refreshes in the background; unknown currencies and unreachable dates fail cleanly with a non-zero exit; clean SIGPIPE handling (no panic when piped into head).

Usage

fx [amount] FROM TO [date]
fx 100 usd eur              # 100 USD -> EUR (latest)
fx gb us                    # 1 GBP in USD
fx 10000 jpy eur            # Japanese yen -> EUR
fx 50 GBP JPY               # 50 pounds to yen
fx canadian swiss           # 1 CAD to CHF

fx usd cad yesterday        # rate on a past date
fx 50 usd cad 2025-05-04    # rate on a specific date
fx eur gbp 3w               # 3 weeks ago

fx --refresh                # force a full rates + 90-day history rebuild
fx --help

Dates accept today, yesterday, YYYY-MM-DD, or a relative N{d,w,m,y} (e.g. 7d, 2w, 6m, 1y). Amounts may contain ,/_ separators.

Anomaly indicators

Signal Meaning
↑ ... near 90d high FROM is near its 90-day high against TO
↓ ... near 90d low FROM is near its 90-day low against TO
→ ... mid 90d range Rate sits mid-range

How it works

Rates come from the free fawazahmed0 currency-api (EUR-based daily snapshots, no API key), cached under ~/.cache/fx/:

  • history.tsv — one row per day (YYYY-MM-DD + all rates), newest-first. This single file backs both the current conversion (top row) and the 90-day anomaly window. Capped at 120 rows; written atomically.
  • .refresh_stamp — throttles background refreshes.

The hot path is pure local I/O: read the cache file, scan the rows it needs, print. Two network cases are detached, so the foreground does not wait: the first run (which seeds 90 days in the background) and advancing to a new day. One case does block, by necessity: asking for a historical date that is not in the cache fetches that window synchronously, so fx 50 usd cad 2025-05-04 takes roughly 0.2 s the first time and about 2 ms afterwards. Conversions pivot through EUR: amount / rate(FROM) * rate(TO). jsDelivr is primary with a pages.dev fallback CDN.

Install

Requires a Rust toolchain (rustup.rs) and curl on PATH (used only for the rare network fetch; every cached call is pure local I/O).

git clone https://github.com/kidsil/fx-cli.git
cd fx-cli
cargo install --path .          # installs `fx` into ~/.cargo/bin

Or build without installing:

cargo build --release           # -> target/release/fx
cargo test

No dependencies beyond the standard library, so it builds offline. The rare network fetch for rates shells out to curl.

Licence

Dual licensed under either of

at your option. Contributions are accepted under the same dual licence.

Supported currencies

All of them, whatever the currency-api feed carries: every ISO 4217 fiat (USD, GBP, JPY, CAD, AUD, CHF, SEK …), precious metals (XAU, XAG), and crypto (BTC, ETH, …).

About

Fast offline-first currency converter for the terminal: every currency in the feed, historical dates, and a 90-day anomaly gauge. Dependency-free Rust.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages