Metadata-Version: 2.4
Name: signalk-cli
Version: 2.1.0
Summary: Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather
Keywords: signalk,sailing,marine,nmea,boating
Author: jey burrows
Author-email: jey burrows <jrb@rhizomatics.org.uk>
License-Expression: Apache-2.0
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Natural Language :: English
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: click>=8.3.3
Requires-Dist: niquests[ws]>=2.28
Requires-Dist: zeroconf>=0.149.16,<0.150.0
Requires-Dist: pyarrow>=17.0 ; extra == 'feather'
Requires-Python: >=3.13
Project-URL: Homepage, https://github.com/rhizomatics/signalk-cli
Project-URL: Repository, https://github.com/rhizomatics/signalk-cli
Project-URL: Issues, https://github.com/rhizomatics/signalk-cli/issues
Project-URL: Changelog, https://github.com/rhizomatics/signalk-cli/blob/main/CHANGELOG.md
Provides-Extra: feather
Description-Content-Type: text/markdown

# SignalK CLI

Query and explore NMEA and other boat data from SignalK APIs using the command line, and export data as CSV, Apache Arrow Feather, or JSON.

APIs supported:

* [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html). Commands available:
  - list-paths
  - list-providers
  - list-contexts
  - query
  - cardinality
* [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html). Commands available:
  - deltas

## Installation

`signalk-cli` is published to PyPi at https://pypi.org/project/signalk-cli/

Python is required to run this, version 3.13 or above. [uv](https://docs.astral.sh/uv/) is the recommended way to install the package ( and can install Python ) but is not required.

### PyPi

```pip install signalk-cli``` or ```uv pip install signalk-cli```

For Apache Arrow Feather export, use the optional dependency: ```pip install 'signalk-cli[feather]'```

### Local Copy

Requires Python 3.13+ and [uv](https://docs.astral.sh/uv/).

```bash
git clone https://github.com/rhizomatics/signalk-cli
cd signalk-cli
uv sync
```

## Temporary Installation

Use `uv` to run without installing the module permanently, for example:

```bash
uv run --with signalk-cli signalk_cli.history list-providers
```

```bash
uv run --with signalk-cli signalk_cli.stream deltas navigation.position --follow
```

## Running

Run via `python -m signalk_cli.history <command>` or `python -m signalk_cli.stream <command>` or without installing the module with `uv run --with signalk-cli signalk_cli.history <command>`.

## Determining SignalK host name

If no host name is set as an argument, the CLI will look for a `SIGNALK_HOST` environment variable, and failing that attempt to automatically discover the host using mDNS (aka Bonjour) and locally cached (see [Default Caching](#default-caching)).

```bash
export SIGNALK_HOST=192.168.6.99   # http:// is added automatically if omitted
```

## Built-in Help

Run with no arguments to list available commands:

```
$ python -m signalk_cli.history
Usage: signalk_cli.history [OPTIONS] COMMAND [ARGS]...

  SignalK v2 history CLI.

Commands:
  cardinality     Compute per-path value statistics for the given time range.
  list-contexts   List contexts that have historical data for the given time range.
  list-paths      List paths that have data for the given time range.
  list-providers  List registered history providers.
  query           Query history and write results as CSV, Feather, or JSON.
```

## Commands


### `query`

Fetch historical values for one or more paths and write to stdout (default) or a file. Aggregation can be controlled in the same way as the History API itself
or a default form where `min_value`,`avg_value` and `max_value` are returned for every period.

```
python -m signalk_cli.history query [OPTIONS] PATH...
```

**PATH** arguments may be:
- **Literal paths** — e.g. `navigation.speedOverGround`
- **Regex / glob patterns** — any argument containing metacharacters (`*`, `.`, `[`, `(`, etc.) is matched against the server's `/paths` endpoint. Bare `*` is treated as a glob wildcard.
- **Inline path specs** — `path:method` or `path:method:param`, e.g. `navigation.speedOverGround:sma:5`. These pass through to the server unchanged.

#### Options

| Option | Default | Description |
|---|---|---|
| `--host` | `$SIGNALK_HOST` | Server base URL. `http://` added if scheme omitted. |
| `--from DATETIME` | — | Start of range (ISO 8601, e.g. `2026-05-26T00:00:00Z`) |
| `--to DATETIME` | now | End of range (ISO 8601) |
| `--duration DURATION` | — | Duration as seconds (`3600`) or ISO 8601 duration (`PT1H`, `P1D`, `P1W`). Combined with `--from` or `--to`, or alone for a window ending now. |
| `--resolution RESOLUTION` | server default | Sample window size: seconds or time expression (`1s`, `1m`, `1h`, `1d`). |
| `-c, --context TEXT` | `vessels.self` | SignalK context |
| `--provider TEXT` | fetched & cached | History provider plugin name, for example `signalk-parquet` |
| `--no-cache` | — | Ignore the cached default provider |
| `--aggregation / --agg` | — | Aggregation method: `average`, `min`, `max`, `first`, `last`, `mid`, `middle_index`, `sma`, `ema`. Omit for wide mode (see below). |
| `--samples N` | server default | Sample count for `--agg sma` |
| `--alpha FLOAT` | server default | Alpha (0–1) for `--agg ema` |
| `--format [csv\|feather\|json\|raw]` | from extension, else csv | Output format. Auto-detected from `.feather`/`.arrow`/`.fea` and `.json` extensions. `feather` requires `pip install 'signalk-cli[feather]'`. |
| `--no-header` | — | Suppress the CSV header row |
| `-o / --output [FILE]` | stdout | Write to a file. Omit the filename (`--output` alone) to auto-name as `signalk-history-<server>-<timestamp>.<ext>`. Use `-` for stdout explicitly. Feather cannot be written to stdout. |
| `--pretty` | — | Pretty-print JSON output with indentation. **Warning:** disables streaming; the full response is buffered in memory before writing. |
| `--bare` | — | Print to stdout with **no informational messages** (server, provider, progress, row count). Ideal for piping to other tools. Not supported with feather. |

If no time range is given, the tool defaults to the hour ending now.

#### Duration normalisation

SignalK only accepts time-only ISO 8601 durations (`PT1H`, `PT30M`, etc.). Durations with date components (`P1D`, `P1W`, `P1Y`, `P1M`) are automatically expanded to explicit `--from`/`--to` timestamps:

- `--duration P1D` alone → `from = now − 1 day`, `to = now`
- `--from T --duration P1D` → `to = T + 1 day`
- `--to T --duration P1W` → `from = T − 1 week`

Integer seconds (`3600`) are passed through unchanged.

#### Output formats

**csv** (default): tabular output as comma-separated values.

**feather**: Apache Arrow Feather binary format, readable with pandas, Polars, R, pyarrow, etc. Requires `pip install 'signalk-cli[feather]'`. Cannot be written to stdout.

**json**: tabular records as JSON objects — same columns as CSV, formatted as a JSON array. Extension `.json` auto-selects this format.

**raw**: exact API response body as received from the server — no Python JSON parse/re-serialize. Streamed directly to stdout unless `--pretty` is used.

#### Output columns

**Wide mode** (default — no `--aggregation` and no inline specs): fetches `min`, `average`, and `max` for each scalar path and writes them as separate columns:

```
timestamp, path, min_value, avg_value, max_value
```

**Array-valued paths in wide mode**: paths whose values are arrays (non-scalar) cannot be meaningfully aggregated with min/max/average. They are requested with a single passthrough method instead and the array elements are expanded into named columns. The column names depend on the path:

| Path pattern | Array length | Columns |
|---|---|---|
| `navigation.*.position` or `navigation.position` | 2 | `longitude`, `latitude` |
| any other array path | N | `value_0`, `value_1`, …, `value_N-1` |

Example for `navigation.position`:

```
timestamp, path, latitude, longitude
```

**Narrow mode** (explicit `--aggregation` or inline path specs): single value column:

```
timestamp, path, value
```

Structured values (positions, arrays) are JSON-encoded in the value column.

#### Examples

```bash
# Last hour of speed — wide mode (min/max/avg columns), printed to stdout
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    navigation.speedOverGround

# Write to auto-named file
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H --output \
    navigation.speedOverGround

# Write to named file
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    --output may26.csv navigation.speedOverGround

# Last day (date-component duration, auto-expanded to from/to)
python -m signalk_cli.history query --host 10.36.10.21 --duration P1D \
    navigation.speedOverGround

# Simple moving average (5 samples), narrowed to one value column
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    --agg sma --samples 5 \
    navigation.speedOverGround

# EMA with alpha 0.2
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    --agg ema --alpha 0.2 \
    navigation.speedOverGround

# Inline spec — path:method, multiple paths with different methods
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    'navigation.speedOverGround:max' 'navigation.courseOverGroundTrue:average'

# Multiple literal paths, 1-minute resolution
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H --resolution 1m \
    navigation.speedOverGround navigation.courseOverGroundTrue

# All navigation paths, specific date range, write to named file
python -m signalk_cli.history query --host 10.36.10.21 \
    --from 2026-05-26T00:00:00Z --to 2026-05-27T00:00:00Z \
    --output may26.csv \
    'navigation\..*'

# Glob wildcard — all paths for the last 30 minutes as Feather (auto-named)
python -m signalk_cli.history query --host 10.36.10.21 --duration PT30M \
    --format feather --output '*'

# Extension auto-selects feather format
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    -o out.feather navigation.speedOverGround

# Exact API response body to stdout (streamed, no informational noise)
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H --format raw --bare \
    navigation.speedOverGround

# Pretty-printed raw JSON (buffered — avoid for large responses)
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    --format raw --pretty navigation.speedOverGround

# Extension auto-selects JSON format
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    -o out.json navigation.speedOverGround

# Suppress header, pipe to another tool
python -m signalk_cli.history query --host 10.36.10.21 --duration 3600 --no-header \
    navigation.speedOverGround | cut -d, -f1,3

# --bare: pure CSV output, no informational noise — pipe-friendly
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H --bare \
    navigation.speedOverGround | awk -F, 'NR>1 {print $2, $3}'

# Different context
python -m signalk_cli.history query --host 10.36.10.21 --duration PT1H \
    -c vessels.urn:mrn:imo:mmsi:123456789 navigation.speedOverGround
```

---

### `cardinality`

Fetch raw values for one or more paths and compute per-path statistics. Useful for exploring a dataset before querying it.

```
python -m signalk_cli.history cardinality [OPTIONS] PATH...
```

PATH arguments follow the same rules as `query` (literal paths, regex/glob patterns, inline specs).

#### Output columns

| Column | Description |
|---|---|
| `path` | SignalK path |
| `distinct_values` | Count of unique values (by string representation) |
| `min` | Minimum value (scalar numeric only; blank otherwise) |
| `max` | Maximum value (scalar numeric only; blank otherwise) |
| `average` | Mean value (scalar numeric only; blank otherwise) |
| `distinct_values_2_decimal_places` | Distinct count after rounding to 2 dp (scalar only; blank otherwise) |
| `nulls` | Count of null/missing values |

For non-scalar paths (e.g. `navigation.position` which returns a lat/lon pair), `min`, `max`, `average`, and `distinct_values_2_decimal_places` are left blank; `distinct_values` still counts unique array representations.

#### Options

Accepts the same `--host`, `--from`/`--to`/`--duration`, `--resolution`, `--context`, `--provider`/`--no-cache`, `--format [csv|json]`, `--no-header`, and `--bare` options as `query`.

```bash
# Statistics for speed over the last hour
python -m signalk_cli.history cardinality --host 10.36.10.21 --duration PT1H \
    navigation.speedOverGround

# All navigation paths, last 24 hours
python -m signalk_cli.history cardinality --host 10.36.10.21 --duration P1D \
    'navigation\..*'

# JSON output
python -m signalk_cli.history cardinality --host 10.36.10.21 --duration PT1H \
    --format json navigation.speedOverGround navigation.position
```

---

### `list-paths`

List all SignalK paths that have recorded data in a given time range.

```
python -m signalk_cli.history list-paths [OPTIONS]
```

Outputs one path per line to stdout by default. Defaults to the last hour if no time range is given. Accepts the same `--from`/`--to`/`--duration`, `--provider`/`--no-cache`, `-c/--context`, and `--bare` options as `query`.

#### Options

| Option | Default | Description |
|---|---|---|
| `--format [csv\|json\|raw]` | `csv` | `csv`: one item per line with `path` header. `json`: `[{"path": ...}]`. `raw`: exact API response body. |

```bash
# Paths recorded in the last hour
python -m signalk_cli.history list-paths --host 10.36.10.21

# Paths available on a specific day
python -m signalk_cli.history list-paths --host 10.36.10.21 \
    --from 2026-05-26T00:00:00Z --to 2026-05-27T00:00:00Z

# Pipe into grep
python -m signalk_cli.history list-paths --host 10.36.10.21 --duration PT24H | grep navigation

# Exact API response body, no informational noise
python -m signalk_cli.history list-paths --host 10.36.10.21 --format raw --bare
```

---

### `list-providers`

List all registered history provider plugins and identify the default. Supports `--bare` to suppress the "Server:" and provider-count lines.

```
python -m signalk_cli.history list-providers --host 10.36.10.21
```

Example output:

```
provider,isDefault
signalk-parquet,True
kip,False
2 provider(s)
```

The default provider is used automatically when `--provider` is not specified on other commands. It is fetched once and cached in `~/.cache/signalk-cli/`.

#### Options

| Option | Default | Description |
|---|---|---|
| `--format [csv\|json\|raw]` | `csv` | `csv`: `provider,isDefault` rows. `json`: `[{"provider": ..., "isDefault": ...}]`. `raw`: exact API response body. |

```bash
# Exact API response body, no informational noise
python -m signalk_cli.history list-providers --host 10.36.10.21 --format raw --bare
```

---

### `list-contexts`

List SignalK contexts (vessels, aircraft, etc.) that have recorded data in a given time range.

```bash
python -m signalk_cli.history list-contexts [OPTIONS]
```

Defaults to the last hour if no time range is given.

#### Options

| Option | Default | Description |
|---|---|---|
| `--format [csv\|json\|raw]` | `csv` | `csv`: one item per line with `context` header. `json`: `[{"context": ...}]`. `raw`: exact API response body. |

```bash
python -m signalk_cli.history list-contexts --host 10.36.10.21

python -m signalk_cli.history list-contexts --host 10.36.10.21 \
    --from 2026-05-26T00:00:00Z --to 2026-05-27T00:00:00Z

# Exact API response body, no informational noise
python -m signalk_cli.history list-contexts --host 10.36.10.21 --format raw --bare
```

---

## SignalK v1 Streaming API

The `signalk_cli.stream` module connects to a running SignalK server's live delta
WebSocket feed (as opposed to `signalk_cli.history`, which queries recorded history).

### `deltas`

Stream live delta updates from the SignalK v1 Streaming API.

```
python -m signalk_cli.stream deltas [OPTIONS] [PATH...]
```

By default, prints the next delta message and exits — useful for a quick check.
Use `--follow` to keep tailing until interrupted with Ctrl-C, optionally capped
with `--count`.

**PATH** arguments are sent verbatim to the server as an explicit subscription,
one per path. They may be literal SignalK paths (e.g. `navigation.speedOverGround`)
or use the SignalK subscription wildcard `*`, matched server-side per the
[Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)
— unlike `history`'s PATH patterns, which the client resolves by matching
against the server's enumerated `/paths` list:

- `*` at the end of a path matches any suffix, e.g. `navigation.*`
- `*` as a middle segment matches any single segment there, e.g. `propulsion.*.oilTemperature`
- a bare `*` subscribes to every path in the context

Quote wildcarded paths (e.g. `'navigation.*'`) so the shell doesn't expand them
against local filenames first.

`deltas` always sends its own explicit subscribe message for `--context`
(default `vessels.self`), covering PATH arguments if given, otherwise every
path (`*`). `--context` also accepts the SignalK wildcard: `'*'` or
`'vessels.*'` subscribes to every vessel, not just your own. `--policy`,
`--period`, and `--min-period` control that subscription per the
[Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)'s
`policy`/`period`/`minPeriod` fields:

- **`--policy`** (`instant` / `ideal` / `fixed`, default `ideal`): `instant` sends every change (throttled by `--min-period`); `ideal` behaves like `instant` but resends the last value if nothing changes within `--period`; `fixed` always sends the last known value every `--period`, regardless of changes.
- **`--period`** (seconds, default `60`): the resend interval for `ideal`/`fixed`. Converted to milliseconds on the wire.
- **`--min-period`** (seconds): fastest allowed transmission rate, only meaningful with `--policy instant`.

The protocol also defines a per-path `format` field (`delta`/`full`), but
this CLI doesn't expose it: signalk-server rejects `full` outright ("Only
delta format supported, using it") and always sends delta messages
regardless, so the choice would be misleading.

**`--subscribe` is a separate, easily-confused mechanism**: it's the
connection-level `subscribe` query parameter (`none`/`self`/`all`), which
controls whether the server *additionally* auto-subscribes the connection
at **its own** default policy/period — you don't get to choose the rate.
This CLI defaults it to `none`, diverging from the SignalK spec's own
default of `self`, because `deltas` always sends its own explicit `--context`
subscription anyway; leaving the connection-level default at `self` would
just double up on updates for your own vessel.

So there are two different ways to see every vessel, and they're not
interchangeable:

| Want | Use | Policy/period used |
|---|---|---|
| Just your own vessel | (defaults) | Yours (`--policy`/`--period`) |
| Every vessel, at your chosen rate | `--context '*'` | Yours (`--policy`/`--period`) |
| Every vessel, at whatever rate the server defaults to | `--subscribe all` | The server's own default |

#### Options

| Option | Default | Description |
|---|---|---|
| `--host` | `$SIGNALK_HOST` | Server base URL. `http://` added if scheme omitted; converted to `ws://`/`wss://` for the stream connection. |
| `--no-cache` | — | Ignore the cached host |
| `-c, --context TEXT` | `vessels.self` | SignalK context |
| `--subscribe [none\|self\|all]` | `none` | Connection-level subscribe policy (SignalK's own `subscribe` query parameter); see above |
| `--policy [instant\|ideal\|fixed]` | `ideal` | Per-path subscribe `policy` field; see above |
| `--period SECONDS` | `60` | Per-path subscribe `period` field, in seconds (converted to ms) |
| `--min-period SECONDS` | — | Per-path subscribe `minPeriod` field, in seconds (converted to ms); only meaningful with `--policy instant` |
| `--format [csv\|json\|raw\|feather]` | from extension, else csv | Output format. `json` is JSON Lines (one row object per line, suitable for a live stream). `raw` is the exact delta message text, one per line. `feather` requires `pip install 'signalk-cli[feather]'` and `--output` (cannot stream to stdout). |
| `--no-header` | — | Suppress the CSV header row |
| `--include-meta` | — | Also emit rows for `meta` entries (units, description, zones, etc.), not just `values`. Adds a `kind` column (`value`/`meta`) to csv/json/feather output. Ignored for `--format raw`, which always includes meta as-is. |
| `-o, --output [FILE]` | stdout | Write to a file. Omit the filename (`--output` alone) to auto-name as `signalk-stream-<server>-<timestamp>.<ext>`. Required for `--format feather`. |
| `-f, --follow` | — | Keep streaming until interrupted (Ctrl-C) or `--count` is reached. Without this, print the next message then exit. |
| `-n, --count N` | 1 without `--follow`, unlimited with it | Number of delta messages to output |
| `--bare` | — | Print to stdout with **no informational messages**. Ideal for piping to other tools. |

#### Output formats

**csv** / **json**: `timestamp, context, source, path, value` — one row per `path`/`value` pair in each delta's updates, written incrementally as messages arrive. Structured values (e.g. `navigation.position`) are JSON-encoded in the `value` column. With `--include-meta`, a `kind` column (`value`/`meta`) is inserted before `value`, and `meta` entries (units, description, zones, etc. — also JSON-encoded) are included as rows too; without it, `meta` entries are silently dropped from csv/json/feather (they're still present in `raw`).

**raw**: the exact delta message JSON as received from the server, one message per line.

**feather**: Apache Arrow Feather binary format, same columns as csv/json. Requires `pip install 'signalk-cli[feather]'`. Unlike the other formats, rows are buffered in memory across all received messages and written once the session ends (`--count` reached, or Ctrl-C with `--follow`) — cannot be streamed to stdout.

#### Examples

```bash
# Next update for one path, then exit
python -m signalk_cli.stream deltas --host 10.36.10.21 navigation.speedOverGround

# Tail all navigation updates until Ctrl-C
python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'navigation.*'

# Tail oil temperature across every engine (mid-path wildcard)
python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'propulsion.*.oilTemperature'

# Next 20 messages across all subscribed paths, as JSON Lines
python -m signalk_cli.stream deltas --host 10.36.10.21 --format json --count 20

# Tail two specific paths, piping raw deltas to another tool
python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --format raw --bare \
    navigation.speedOverGround navigation.courseOverGroundTrue

# Everything the server has (equivalent to ?subscribe=all), following
python -m signalk_cli.stream deltas --host 10.36.10.21 --subscribe all --follow

# Faster resend interval (5s instead of the 60s default)
python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --period 5 'navigation.*'

# Send every change immediately, no more than 5 times/second
python -m signalk_cli.stream deltas --host 10.36.10.21 --follow \
    --policy instant --min-period 0.2 navigation.speedOverGround

# Capture 500 messages to an auto-named Feather file
python -m signalk_cli.stream deltas --host 10.36.10.21 --count 500 --format feather --output 'navigation.*'

# Capture until Ctrl-C to a named Feather file
python -m signalk_cli.stream deltas --host 10.36.10.21 --follow -o capture.feather 'navigation.*'
```

---

## Time range

All commands that query data accept the same three time parameters. At least one of `--from` or `--duration` must be provided (or the tool supplies a one-hour default).

| Parameter | Format | Examples |
|---|---|---|
| `--from` | ISO 8601 timestamp | `2026-05-26T00:00:00Z` |
| `--to` | ISO 8601 timestamp | `2026-05-27T00:00:00Z` |
| `--duration` | ISO 8601 duration or integer seconds | `PT1H`, `PT15M`, `P1D`, `P1W`, `3600` |

Typical combinations:

- `--duration PT1H` — last hour ending now
- `--from T --duration PT1H` — hour starting at T
- `--from T1 --to T2` — explicit range
- `--duration PT1H --to T` — hour ending at T
- `--duration P1D` — last 24 hours (expanded to from/to automatically)

## Default caching

The default history provider is fetched from the server once and cached per host in `~/.cache/signalk-history-cli/`. Pass `--no-cache` to force a fresh lookup, or `--provider <id>` to target a specific provider explicitly.
