Terminal weather client. Pluggable weather backends (keyless first, BYOK for the commercial ones), wttr.in-style output, city-name geocoding, optional IP-based location, configurable units and output language, XDG-compliant state, no telemetry.
$ cirrocast Beijing --lang en-US --color never --width 100 -d 3
Weather report: Beijing, Beijing Municipality, China (39.91, 116.40)
· * · Clear sky
(●) +20°C (+16°C)
* · * ↙ 11km/h SW
29% 1021hPa 18km 0.0mm
┌───────────────────────┬───────────────────────┬───────────────────────┐
│ Today, Oct 06 │ Wed 07 Oct │ Thu 08 Oct │
├───────────────────────┼───────────────────────┼───────────────────────┤
│ \│/ Morning │ \│/ Morning │ \│/ Morning │
│ ─(●)─ +18°C (+14°C) │ ─(●)─ +17°C (+16°C) │ ─(●)─ +17°C (+16°C) │
│ /│\ ↑ 7.0km/h N │ /│\ ↗ 1.5km/h NE │ /│\ ↗ 3.1km/h NNE │
│ 0.0mm 0% │ 0.0mm 0% │ 0.0mm 0% │
└───────────────────────┴───────────────────────┴───────────────────────┘
One line, pipe-friendly records, or the machine-readable document:
$ cirrocast Beijing -f one-line --lang en-US --color never --width 100 -d 3
Beijing: *o* Clear sky +20°C (+16°C), ↙ 11km/h SW, 29%, 0.0mm, 1021hPa, 18km
$ cirrocast Beijing -f plain --lang en-US --color never --width 100 -d 3 | head -2
location: Beijing, CN (39.91, 116.40) Asia/Shanghai
current: Clear sky 20°C (feels 16°C) wind 11km/h SW humidity 29% precip 0.0mm pressure 1021hPa visibility 18km
Both blocks are abridged captures of real runs, taken on 2026-10-06 with the flags shown (the full
captures, one per format, live in docs/screenshots/); the format reference is
docs/formats.md. An ambiguous name prints a note: … candidates … line to
stderr, so stdout stays pipeable — :Beijing demands an exact match and --pick asks which one.
Every scheduled feature is implemented and released: the v1 surface (2026-10-02), severe-weather
alerts, air quality and moon/astro, the offline city database with user-installed table updates,
multi-location output with the interactive picker, the status probe, the additional and keyless
national backends, climate normals, and QWeather's Ed25519 JWT. The current release and its history
are in CHANGELOG.md; what remains unscheduled — the wttr-compatible local service
and the wider packaging matrix — is marked as backlog in docs/plans/.
cargo install --locked cirrocast # from crates.io (1.5.0 is published)
cargo install --locked --path . # from this checkout
paru -S cirrocast # Arch: the AUR package (yay, or a manual git clone +
# makepkg); https://aur.archlinux.org/packages/cirrocast
# Prebuilt archives for Linux (x86_64, aarch64) and macOS (Apple silicon), one per release:
version=v1.5.0 target=x86_64-unknown-linux-gnu
curl -LO "https://github.com/YangtseSu/cirrocast/releases/download/$version/cirrocast-$version-$target.tar.gz"
sha256sum -c "cirrocast-$version-$target.tar.gz.sha256"
tar xzf "cirrocast-$version-$target.tar.gz"
install -Dm755 "cirrocast-$version-$target/cirrocast" ~/.local/bin/cirrocastcargo install places the binary and nothing else, so the man page and the completions are
generated by the binary itself:
cirrocast man > ~/.local/share/man/man1/cirrocast.1
cirrocast completion bash > ~/.local/share/bash-completion/completions/cirrocast # bash-completion ≥ 2.9
cirrocast completion zsh > ~/.local/share/zsh/site-functions/_cirrocast # add that directory to $fpath
cirrocast completion fish > ~/.config/fish/completions/cirrocast.fishPackaging and release has the full picture: what each path
installs, how the archives are built and verified, and the checklist a release follows. Further
package formats are deliberately unscheduled (backlog B02 in docs/plans/),
and the first-run walkthrough is docs/getting-started.md.
cirrocast [OPTIONS] [LOCATION]...
-p, --provider <LIST> provider chain, or `auto` (coverage-ranked keyless backends)
-f, --format <NAME> art-table | one-line | plain | json | dumb | alerts | aqi | moon
| normals, a one-line preset, or a [templates] key
-d, --days <N> 0..=14, clamped to what the provider serves
-u, --units <SYSTEM> metric | us | uk --lang <TAG> BCP-47, or auto
--tz <ZONE> IANA zone for the report's times (e.g. Asia/Shanghai)
--lat/--lon <DEG> coordinates instead of a location argument
--ip locate from the public IP (opt-in; see Privacy below)
--station <ICAO> METAR station; `metar` heads the provider chain
--alerts/--no-alerts, --severity <LEVEL>, --alerts-from <LIST>
--aqi [--aqi-index us|european|cn] [--air-source auto|open-meteo|qweather],
--moon, --normals, --marine
--date <YYYY-MM-DD>, --history <Nd> the archive instead of a forecast
--template <TPL>, --template-file <PATH> one-line templates
--offline[=weather|geo|all], --no-cache, --refresh, --timeout <SECS>
--pick/--yes, --color <WHEN>, --width <COLS>, --icons <SETS>, -q/--quiet, -v/--verbose
The full flag matrix is cirrocast --help; a byte-for-byte copy of that page is
docs/reference/help-long.txt and the flat list of every long flag
is docs/reference/flags.txt.
Exit codes: 0 success · 1 generic · 2 usage · 3 network or upstream · 4 configuration or
state on disk · 5 location not found · 6 missing or invalid API key.
Examples:
cirrocast Beijing Shanghai Tokyo # several places, fetched four at a time, printed in order
cirrocast --provider auto -v Wuhan # show the coverage-ranked chain for the resolved place
cirrocast -f json Beijing | jq .current.temp_c
cirrocast --station KJFK # a METAR observation instead of a forecast
cirrocast status -f '%c %t' --max-age 900 # one line for a status bar; see docs/ecosystem.md--format |
What it is | Width |
|---|---|---|
art-table |
the wttr.in-style day-part table (default) | honours --width; stacks below 60 columns |
dumb |
art-table in 7-bit ASCII, no colour; automatic for TERM=dumb or a non-UTF-8 locale |
as above |
one-line |
one line driven by % tokens (full, minimal, short, default, uv, sun are presets) |
fixed |
plain |
box-free label: value records |
ignores --width |
json |
the stable machine-readable document | ignores --width/--units |
alerts |
the full severe-weather warning listing, strongest first | ignores --width |
aqi |
the standalone air-quality panel (implies --aqi) |
wraps to --width |
moon |
the standalone moon/sun view (implies --moon) |
wraps to --width |
normals |
the month's climate normal against the forecast (implies --normals) |
wraps to --width |
One captured output per format is in docs/screenshots/ — art-table.txt,
one-line.txt, plain.txt, json.txt, dumb.txt, alerts.txt, aqi.txt, moon.txt and
normals.txt, all plain text with no escape sequences. The token table, the width/precision syntax
and the unknown-token policy are in docs/formats.md; the json document's
contract and key index are in docs/schema.md; the status probe — one line,
never non-zero because of the network — is in docs/ecosystem.md.
The hand-drawn blocks are the default because they need no font beyond a Latin monospace.
--icons emoji draws one Unicode emoji per condition, and --icons nerd one Weather Icons glyph
(U+E300–U+E3E3) from a Nerd Font; both are opt-in:
cirrocast --icons emoji Beijing # ☀️ ⛅ 🌧️ in the table, borders unchanged
cirrocast --icons nerd Beijing # needs a patched font; codepoints only otherwise
cirrocast --icons nerd,emoji Beijing # the chain: Nerd Font first, emoji where it lacks
cirrocast --icons emoji -f one-line --template '%l %c %t' # `%c` follows the setThe value is an ordered chain and blocks always ends it, so a key an icon set lacks falls back to
the hand-drawn art instead of printing tofu. %x keeps printing the 7-bit symbol whatever
--icons says, and plain/json carry no art at all. A terminal that cannot draw UTF-8 gets the
blocks. Nerd Fonts: install a patched font from
ryanoasis/nerd-fonts; the default needs no font. The
key [render] icons and the precedence are in
docs/configuration.md; the corpus rules are in
docs/formats.md.
Beijing (fuzzy, ranked), :Beijing (exact name), ~Tsinghua (OpenStreetMap/Nominatim),
@39.9042,116.4074 (coordinates, no geocoding request) and @home (an alias from [locations]);
no argument means location.default, else the public-IP lookup. A plain name resolves from the
bundled city table first (about 3.3 MiB compressed, no network) and only falls back to the network
geocoder on a miss; location search prints the winner and --all the ranked candidates, and
location update-data installs a newer table per user.
The network geocoder is Open-Meteo by default, with the GeoNames fuzzy index (BYOK) and Nominatim
behind it; [geo] search = "qweather" pins QWeather's own GeoAPI instead, which resolves a
Chinese district or township to the point QWeather serves weather for (新乡县, not its parent city)
under the same credential and host the qweather backend uses. It is explicit-only — never part of
auto, because its rows carry no ISO country code — and, per QWeather's terms, keeps no cache entry
at all: every query is a fresh request.
Privacy: the public-IP lookup is the only request that reveals anything about you rather than
about a place you asked for, and it never happens implicitly — it runs only with --ip or when no
location is configured anywhere. It sends the public IP to ipwho.is (falling back to ipapi.co
and IP.SB), caches the answer for 24 hours and names the service it used on stderr.
The syntax forms, the ranking rules and the picker are in
docs/location.md.
cirrocast location update-data # the official GeoNames cities15000 dump
cirrocast location update-data --from ~/Downloads/cities15000.zip # a local .txt or .zip
cirrocast location update-data --check # only report whether it would change (exit 1 if so)The table is built with the repository's own encoder, proven to decode and installed atomically
under $XDG_DATA_HOME/cirrocast/geo/; [geo] data picks auto (the user table when one is
installed and valid, else the bundled one), bundled or user. Nothing in a query ever fetches
city data: [geo] update = "check" prints a once-a-day note when the answering table is older than
update_interval_days, so refreshing is yours to schedule — a timer running the idempotent command
above is enough. The GeoNames credit prints for either table.
| ID | Key | Coverage | Observation | Forecast | Max days |
|---|---|---|---|---|---|
open-meteo |
none | global | yes | yes | 16 |
met-no |
none | global | yes | yes | 9 |
smhi |
none | Nordics and adjacent seas | yes | yes | 10 |
nws |
none | the US and its territories | no | yes | 7 |
brightsky |
none | Germany (DWD open data) | yes | yes | 10 |
metar |
none | worldwide stations | yes | no | — (observation) |
open-meteo-archive |
none | global | no | archive | — (1940-01-01 onward, --date/--history) |
open-meteo-marine |
none | coastal waters | supplement | supplement | 8 (waves and swell, --marine) |
openweathermap |
CIRROCAST_OPENWEATHERMAP_KEY |
global | yes | yes | 5 |
weatherapi |
CIRROCAST_WEATHERAPI_KEY |
global | yes | yes | 3 |
worldweatheronline |
CIRROCAST_WORLDWEATHERONLINE_KEY |
global | yes | yes | 5 |
pirateweather |
CIRROCAST_PIRATEWEATHER_KEY |
global | yes | yes | 7 |
qweather |
CIRROCAST_QWEATHER_KEY |
global | yes | yes | 10 |
visualcrossing |
CIRROCAST_VISUALCROSSING_KEY |
global | yes | yes | 15 |
auto ranks the keyless backends by coverage for the resolved place — an exact country match first
(nws for a US point, brightsky for a German one), then a containing bounding box (smhi in the
Nordics), then the global entries (open-meteo, met-no) — and -v prints the chain it chose. A
chain falls through to the next backend only when the failure is transport-level or comes from
upstream (a timeout, a 429, a 5xx); a usage, configuration, location or credential error stops
the walk. The air-quality panel picks its source the same way the alert registry does: auto
prefers QWeather in mainland China when that provider is on the chain and holds a credential, and
answers from the keyless Open-Meteo everywhere else (--air-source, [air] source), so
--aqi-index cn prints the Chinese national AQI wherever it exists.
Keys are BYOK and never enter config.toml (see API keys). Every rendered report
carries the credit its data licence asks for, and nothing is redistributed. Alerts are a separate
source registry selected by coverage (nws, meteoalarm, qweather, hko, wmoswic, fpas,
visualcrossing), and the air-quality, marine and climate-normals panels are supplementary fetches,
never chain entries.
cirrocast ships no data of its own: endpoints, request parameters, free-tier quotas with their
exact wording, licence duties, caching ceilings and traps are recorded per provider in
docs/providers.md; provider list and provider info <ID> print the
machine-readable subset from the binary.
Everything lives under the XDG directories: $XDG_CONFIG_HOME/cirrocast/config.toml (default
~/.config/cirrocast/config.toml), API keys in keys.toml beside it, the cache in
$XDG_CACHE_HOME/cirrocast/ and data in $XDG_DATA_HOME/cirrocast/. When no user file exists,
$XDG_CONFIG_DIRS (default /etc/xdg) is searched for a system-wide one.
Precedence, highest first: command line flag → CIRROCAST_* environment variable → config.toml
→ built-in default. cirrocast config get <KEY> prints the effective value, environment override
included.
cirrocast config path # where the file lives (creates nothing)
cirrocast config init [--force] # annotated default document
cirrocast config set defaults.days 5 && cirrocast config show
cirrocast config validate # ok: ~/.config/cirrocast/config.toml
cirrocast config edit # $VISUAL/$EDITOR, re-validated afterwards[locations] names @aliases and [templates] names %-token templates; both are yours:
schema_version = 2
[locations]
home = "@39.9042,116.4074" # cirrocast @home (aliases may chain: office = "@home")
office = ":Shanghai" # cirrocast @office
[templates]
compact = "%c%t" # cirrocast -f compact / --template @compactEvery key — type, default, example and effect — is in
docs/configuration.md; the schema and its migration rule are in
docs/schema.md.
Provider keys are BYOK and never enter config.toml (which is world readable, shown by config show and hand edited). First hit wins: CIRROCAST_<PROVIDER>_KEY (provider id upper-cased, - →
_, e.g. CIRROCAST_OPENWEATHERMAP_KEY) → keys.toml in the configuration directory. There is no
third tier (an OS keyring is out of scope for v1).
printf %s "$CIRROCAST_OPENWEATHERMAP_KEY" | cirrocast key set openweathermap
cirrocast key list # openweathermap sk-t…56 (file)
cirrocast key rm openweathermapkey set reads the secret from stdin only — never from the command line, where the process list and
the shell history would see it — prompting on the terminal with echo disabled when stdin is a
terminal. The stored file is mode 0600; a file that group or other can read is refused with the
chmod 600 fix instead of being used, and key list prints masked values only. QWeather also
accepts an Ed25519 JWT (key set qweather --jwt --key-file …); the JWT commands, the MeteoAlarm
token and the GeoNames account name are in
docs/getting-started.md, with the failure modes in
docs/troubleshooting.md.
| Document | What it covers |
|---|---|
docs/getting-started.md |
install, the first run, config init, a key for one keyed backend, offline mode |
docs/configuration.md |
every configuration key — type, default, example and effect — and the precedence rules |
docs/providers.md |
the per-backend record: endpoints, auth, quotas with their exact wording, attribution duties and traps |
docs/formats.md |
every output format, the % token table and the multi-location output rules |
docs/location.md |
the location argument forms, the ranking rules and the offline city data |
docs/i18n.md |
the shipped catalogs, auto negotiation and how to add a language |
docs/troubleshooting.md |
failures by symptom — network, keys, cache, rate limits, a wrong city — and how to produce a bug report |
docs/architecture.md |
the module map, the request data flow, the on-disk state and the invariants |
docs/performance.md |
the enforced performance and resource budget and how it is measured |
docs/ecosystem.md |
the status probe contract, the json/one-line/plain output contracts and the status-bar recipes |
docs/schema.md |
the JSON and config schemas, the versioning policy and the key index |
The generated reference (help-long.txt, flags.txt) and the JSON Schemas live under
docs/reference/ and docs/schema/, the build plan these
features were delivered under is docs/plans/, and
CHANGELOG.md records each release.
CONTRIBUTING.md is the contributor guide — prerequisites, the build and test
commands, the test policy, the plan-driven workflow, the recipes, and the packaging, publishing and
release checklists.
What shipped when is in CHANGELOG.md; the version numbers a consumer of the json
document or the configuration file cares about, and the rules that move them, are in
docs/schema.md; the build plan and its architecture contract are in
docs/plans/.
GPL-3.0-or-later — see LICENSE. The repository is
REUSE-compliant; per-file copyright and licence information lives in
SPDX headers and in REUSE.toml.