Skip to content
YangtseSuPublic

About

Terminal weather client with pluggable backends and wttr.in-style output

Resources

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

cirrocast

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.

Status

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/.

Install

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/cirrocast

cargo 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.fish

Packaging 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.

Usage

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

Formats

--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.

Icon sets

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 set

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

Location

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.

Updating the city data

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.

Providers

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.

Configuration

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 @compact

Every key — type, default, example and effect — is in docs/configuration.md; the schema and its migration rule are in docs/schema.md.

API keys

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 openweathermap

key 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.

Documentation

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.

Development

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/.

Licence

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.

Acknowledgements

About

Terminal weather client with pluggable backends and wttr.in-style output

Resources

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages