Files
magnus919_agent-skills/tempest/references/cli-worked-recipes.md
T
Magnus Hedemarkandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> ce5f34a33d fix(tempest): honor --dry-run on udp listen without binding a socket
udp listen ignored the universal --dry-run flag and bound UDP 50222,
hanging when no hub is on the LAN. Add a dry-run plan branch that
describes the listen parameters (bind address, port, timeout, show-all)
and exits 0 without creating any socket, so doc claims of universal
--dry-run support stay universal and true.

Four regression tests pin the plan shape, the defaults/--show-all
propagation, and prove no socket is constructed (and no token demanded).
Recipe 5 documents the udp listen plan object alongside the other plans.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-30 01:52:10 -04:00

169 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLI Worked Recipes (tempest)
Multi-step, executable recipes for the bundled `tempest` CLI. Global flags
`--json`, `--dry-run`, `--quiet`, `--verbose` work in any position on the
command line. `--json` output is metric-native (raw wire units); human output
is converted. `--dry-run` never touches the network and always exits 0 with a
plan object.
## Recipe 1: Discover the station, then read current conditions
```bash
# Step 1: find station and device ids (works even before you memorize ids)
tempest stations --json | jq '.stations[] | {station_id, name,
devices: [.devices[] | {device_id, device_type, serial_number}]}'
# Step 2: current conditions, machine-readable
tempest current --json | jq '{station, device_id, type,
temp_c: .observation.air_temperature,
wind_mps: .observation.wind_avg,
rain_mm: .observation.rain_accumulation}'
# Step 3 (pin a specific station/device when several exist)
tempest current --station-id 12799 --device-id 60526 --json
```
Stage compatibility: `stations --json` emits `{"stations": [...]}` with
integer `station_id`/`device_id` fields — feed those ints to
`--station-id`/`--device-id` on `current`. `current --json` emits
`{station, device_id, type, observation}` where `observation` carries the
decoded positional array as named fields (metric-native types: numbers for
measurements, `timestamp` as ISO-8601 string).
Auto-selection rules when you don't pass ids: the first station is used; the
device is the first `ST` (Tempest), then `SKY`/`SK`, then `AIR`/`AR`, always
skipping `HB` hubs (hubs carry no observations). If only a hub exists the CLI
dies with a clear error instead of guessing.
## Recipe 2: 7-day forecast slice for scripts
```bash
tempest forecast --days 7 --json \
| jq '{units_temp: .forecast.units.units_temp,
today: (.forecast.forecast.daily[0]
| {day_start_local, air_temp_high, air_temp_low, precip_probability}),
next12: [.forecast.forecast.hourly[:12][]
| {local_hour, air_temperature, precip_probability}]}'
```
Converting highs to °F with jq (read `units` from the same document before
converting anything):
```bash
tempest forecast --json \
| jq '{units_temp: .forecast.units.units_temp,
highs_f: [.forecast.forecast.daily[] | .air_temp_high * 9 / 5 + 32],
rain_hours: [.forecast.forecast.hourly[] | select(.precip_probability > 30) | .local_hour]}'
```
**Convert only after reading `units`:** the endpoint honors unit overrides
(`units_temp=f` etc.), so hard-coded Celsius math double-converts Fahrenheit
responses. When the CLI displays forecast values it converts °C→°F only for
stations whose `units_temp` is `c`. Human output prints current conditions,
then the daily table, then the next 12 hours.
## Recipe 3: Rain-watch (yesterday's total + live rain events)
```bash
# What fell yesterday (UTC day): obs from history, day_offset=1
DEVICE_ID=$(tempest stations --json | jq -r '
.stations[].devices[] | select(.device_type == "ST") | .device_id' | head -1)
tempest obs --device-id "$DEVICE_ID" --days 1 --json \
| jq '{type, samples: (.observations | length),
day_rain_mm: .observations[-1].local_day_rain_accumulation}'
# Live: rain-start events and rapid wind from the hub broadcast
tempest udp listen --timeout 600 --json | jq 'select(.type == "evt_precip")'
```
Stage compatibility: `obs --json` emits `{device_id, type, count,
observations}` with each decoded observation carrying
`local_day_rain_accumulation` (mm, number) — the `-1` index grabs the newest
sample of the day. `udp listen --json` emits one JSON object per datagram;
`evt_precip` objects carry `{type, serial_number, timestamp}`.
## Recipe 4: Decode any raw UDP datagram positionally
Feed canned datagram bytes to the same decoder the listener uses — no
sockets, no hub required (this is exactly how `scripts/test_tempest.py`
exercises the parser):
```python
# /tmp/decode_one.py
import importlib.machinery, importlib.util, json
loader = importlib.machinery.SourceFileLoader("t", "tempest/scripts/tempest")
spec = importlib.util.spec_from_loader(loader.name, loader)
mod = importlib.util.module_from_spec(spec)
loader.exec_module(mod)
datagram = (b'{"serial_number":"ST-00000512","type":"obs_st","hub_sn":"HB-00013030",'
b'"obs":[[1588948614,0.18,0.22,0.27,144,6,1017.57,22.37,50.26,328,0.03,3,'
b'0.0,0,0,0,2.410,1]],"firmware_revision":129}')
msg = json.loads(datagram.decode())
for row in msg["obs"]: # obs families: list of rows
decoded = mod.decode_obs(row, msg["type"])
print(decoded["air_temperature"], "°C", decoded["air_temperature_unit"])
rapid = json.loads(b'{"type":"rapid_wind","ob":[1493322445,2.3,128],"serial_number":"SK-1"}'.decode())
speed, direction = rapid["ob"][1], rapid["ob"][2] # rapid_wind: ONE array under "ob"
```
The three structural keys to remember (see udp-broadcast-protocol.md):
observation families nest rows under `obs`; `rapid_wind` carries one array
under `ob`; events (`evt_precip`, `evt_strike`) carry one array under `evt`;
`hub_status`/`device_status` have named fields and no array at all. Dispatch
on `type` before indexing.
## Recipe 5: Dry-run previews and flag behavior
```bash
# Plan, don't execute: valid JSON, exit 0, zero network
tempest forecast --station-id 12799 --days 3 --dry-run --json
# -> {"dry_run": true, "command": "forecast", "station_id": 12799, "days": 3}
# Every documented command has a dry-run plan — current, obs, forecast,
# stations, and udp listen (plans the bind, creates no socket, safe off-LAN)
tempest obs --device-id 60526 --days 2 --dry-run --json
tempest udp listen --port 50222 --timeout 30 --dry-run --json
# -> {"dry_run": true, "command": "udp", "subcommand": "listen",
# "bind_address": "0.0.0.0", "port": 50222, "timeout_seconds": 30,
# "show_all": false}
# Quiet/verbose piping: logs on stderr, data on stdout
tempest current --json --quiet | jq .observation.air_temperature
```
Behavior contract: `--dry-run` works without `TEMPEST_TOKEN` set (no credential
needed to see a plan); `--help` and `--dry-run` are always offline. For
`udp listen`, dry-run describes the listen parameters (bind address, port,
timeout, show-all) and exits 0 without creating or binding any socket — the
real listener waits for hub traffic on UDP 50222 and needs the hub's LAN.
Without `--dry-run`, a missing token exits 1 with
`Error: TEMPEST_TOKEN not set...` before any request is attempted.
## Recipe 6: JSON error paths you'll actually see
```bash
tempest current --station-id 99999999
# Error: Station 99999999 not found. (exit 1)
tempest obs --device-id 123 # hub or wrong device
# Error: API error (404): ... (exit 1)
unset TEMPEST_TOKEN; tempest stations
# Error: TEMPEST_TOKEN not set. Get one at https://weatherflow.com (exit 1)
```
The client maps 401 → token message, 403 → access-denied message, 404 →
not-found-with-path, and any other ≥400 dumps the response body. In `--json`
mode errors still go to stderr as text; only success payloads print to stdout,
so `jq` pipelines fail loudly instead of parsing prose.
## Sources
- https://apidocs.tempestwx.com/reference/quick-start (token setup, REST examples, primary-source guidance)
- https://apidocs.tempestwx.com/reference/get_stations (StationSet shape feeding the stations command)
- https://apidocs.tempestwx.com/reference/getobservationsbydeviceid (device observation parameters used by current/obs)
- https://apidocs.tempestwx.com/reference/get_better-forecast-1 (forecast unit selection used by recipe 2)
- https://weatherflow.github.io/Tempest/api/udp/v171/ (UDP message families used by recipes 34)