bywind-cli 0.1.1

Command-line frontend for the `bywind` sailing-route optimiser: search, convert, info, inspect, tune.
# bywind-cli

Command-line frontend for the
[`bywind`](https://crates.io/crates/bywind) sailing-route optimiser.

## Install

```sh
cargo install bywind-cli
```

This installs a single binary, `bywind-cli`. Verify with
`bywind-cli --help` to see the subcommand list.

## Subcommands

### `search`

Run a sailing search and emit a `SavedSolution` JSON. The hard-required
inputs (`<map>`, `--start`, `--end`) can come either as flags or from a
`[run]` section in a `--config` TOML file.

```sh
bywind-cli search forecast.grib2 \
    --start -73.95,40.75 \
    --end   -9.13,38.71 \
    --waypoints 20 \
    --time-weight 1 --fuel-weight 10 --land-weight 40 \
    -o solution.json
```

Multiple `--config` files merge left-to-right; CLI flags override the
merged result. See `--help` for every flag.

### `fetch`

Pull a wind-map window from NOAA's public GFS S3 bucket and write it
directly to disk. Output format is inferred from the `--out` extension:
`.grib2` streams the concatenated GFS messages straight through; `.wcav`
stages a temporary GRIB2 and re-encodes it as AV1 near-lossless so the
final artifact is small enough to ship.

```sh
bywind-cli fetch 20260301 2026030107 --out window.wcav
```

`<start>` and `<end>` are UTC, format `YYYYMMDD` (hour defaults to 00z)
or `YYYYMMDDHH`. `<start>` must be a GFS cycle hour (00, 06, 12, or 18).
`--interval-h N` controls the cadence (1, 2, 3, or 6 hours; default 1
uses each cycle's `f000`..`f005` short-range forecasts for seamless
1-hour cadence). Only the UGRD / VGRD-at-10m messages are fetched via
HTTP Range requests, so total transfer is a couple of MB per frame.

### `convert`

GRIB2 → `wind_av1` (`.wcav`) conversion. Used to bake slow-to-parse
GRIB2 files into the compact AV1 near-lossless binary format so
subsequent searches over the same map skip the (often multi-second)
GRIB2 decode.

```sh
bywind-cli convert forecast.grib2 -o forecast.wcav
```

`--grib-stride N` decimates the lat/lon grid (every Nth row + col);
`--grib-bbox lat_min,lon_min,lat_max,lon_max` clips to a region of
interest before decoding.

### `info`

Print summary metadata about a wind map — frame count, time step,
extent, sample count, projected grid dimensions.

```sh
bywind-cli info forecast.grib2
```

Works on any supported format.

### `inspect`

Print metadata for a saved solution. With `--map`, also re-scores the
solution's gbest path against that wind map and prints a per-segment
breakdown of fuel / time / speed / land.

```sh
bywind-cli inspect solution.json --map forecast.grib2
```

### `tune-trial`

Per-trial worker for the PSO-tuning study. Reads a JSON trial spec
(params + seeds + routes) from stdin, runs the search across
`routes × seeds`, and writes aggregate JSON to stdout. Designed for
spawning by `tune` rather than direct invocation; see
`src/tune_trial.rs` for the schema.

### `tune`

Drives a TPE Bayesian optimisation over the PSO coefficient space by
spawning `tune-trial` as a subprocess per trial. Persists trials as
JSONL so an interrupted study leaves an inspectable record. Run a
baseline pass first via `tune-trial` with default coefficients, then
pass the resulting JSON via `--baseline`.

## Exit codes

| Code | Class | Meaning |
| --- | --- | --- |
| 0 | success | search completed |
| 1 | `BadInput` | CLI / config / file-parse error |
| 2 | `NoResult` | search ran but produced no usable route (landlocked endpoints, empty wind map after filters, etc.) |
| 3 | `Internal` | unexpected error in bywind — bug |

Subcommands return `Result<(), AppError>` where the variants carry the
exit-code class; unannotated `?` from `anyhow` contexts auto-converts
to `BadInput`.

## Features

- `profile-timers` — forwards through to `bywind` / `swarmkit-sailing`.
  Enables sub-stage `Instant::now` counters in the search hot paths;
  emits a per-stage breakdown to stderr at the end of every search.

## License

Dual-licensed under either of

- Apache License, Version 2.0 ([`LICENSE-APACHE`]./LICENSE-APACHE)
- MIT license ([`LICENSE-MIT`]./LICENSE-MIT)

at your option.