signalk-cli 1.2.0__tar.gz → 2.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalk-cli
3
- Version: 1.2.0
4
- Summary: Query the SignalK v2 History API and export data as CSV or Apache Arrow Feather
3
+ Version: 2.0.1
4
+ Summary: Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather
5
5
  Keywords: signalk,sailing,marine,nmea,boating
6
6
  Author: jey burrows
7
7
  Author-email: jey burrows <jrb@rhizomatics.org.uk>
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Programming Language :: Python :: 3.14
17
17
  Requires-Dist: click>=8.3.3
18
- Requires-Dist: niquests>=2.28
18
+ Requires-Dist: niquests[ws]>=2.28
19
19
  Requires-Dist: zeroconf>=0.149.16,<0.150.0
20
20
  Requires-Dist: pyarrow>=17.0 ; extra == 'feather'
21
21
  Requires-Python: >=3.13
@@ -28,14 +28,25 @@ Description-Content-Type: text/markdown
28
28
 
29
29
  # SignalK CLI
30
30
 
31
- Query and explore SignalK APIs from the command line, and export data as CSV, Apache Arrow Feather, or JSON.
31
+ 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.
32
32
 
33
33
  APIs supported:
34
34
 
35
- * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html)
35
+ * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html). Commands available:
36
+ - list-paths
37
+ - list-providers
38
+ - list-contexts
39
+ - query
40
+ - cardinality
41
+ * [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html). Commands available:
42
+ - deltas
36
43
 
37
44
  ## Installation
38
45
 
46
+ `signalk-cli` is published to PyPi at https://pypi.org/project/signalk-cli/
47
+
48
+ 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.
49
+
39
50
  ### PyPi
40
51
 
41
52
  ```pip install signalk-cli``` or ```uv pip install signalk-cli```
@@ -62,12 +73,11 @@ uv run --with signalk-cli signalk_cli.history list-providers
62
73
 
63
74
  ## Running
64
75
 
65
- Run via `python -m signalk_cli.history <command>`.
76
+ 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>`.
66
77
 
67
78
  ## Determining SignalK host name
68
79
 
69
- 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
70
- the host using mDNS (aka Bonjour) and locally cached (see [Default Caching](#default-caching)).
80
+ 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)).
71
81
 
72
82
  ```bash
73
83
  export SIGNALK_HOST=192.168.6.99 # http:// is added automatically if omitted
@@ -395,6 +405,122 @@ python -m signalk_cli.history list-contexts --host 10.36.10.21 --format raw --ba
395
405
 
396
406
  ---
397
407
 
408
+ ## SignalK v1 Streaming API
409
+
410
+ The `signalk_cli.stream` module connects to a running SignalK server's live delta
411
+ WebSocket feed (as opposed to `signalk_cli.history`, which queries recorded history).
412
+
413
+ ### `deltas`
414
+
415
+ Stream live delta updates from the SignalK v1 Streaming API.
416
+
417
+ ```
418
+ python -m signalk_cli.stream deltas [OPTIONS] [PATH...]
419
+ ```
420
+
421
+ By default, prints the next delta message and exits — useful for a quick check.
422
+ Use `--follow` to keep tailing until interrupted with Ctrl-C, optionally capped
423
+ with `--count`.
424
+
425
+ **PATH** arguments are sent verbatim to the server as an explicit subscription,
426
+ one per path. They may be literal SignalK paths (e.g. `navigation.speedOverGround`)
427
+ or use the SignalK subscription wildcard `*`, matched server-side per the
428
+ [Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)
429
+ — unlike `history`'s PATH patterns, which the client resolves by matching
430
+ against the server's enumerated `/paths` list:
431
+
432
+ - `*` at the end of a path matches any suffix, e.g. `navigation.*`
433
+ - `*` as a middle segment matches any single segment there, e.g. `propulsion.*.oilTemperature`
434
+ - a bare `*` subscribes to every path in the context
435
+
436
+ Quote wildcarded paths (e.g. `'navigation.*'`) so the shell doesn't expand them
437
+ against local filenames first.
438
+
439
+ `deltas` always sends its own explicit subscribe message for `--context`,
440
+ covering PATH arguments if given, otherwise every path (`*`). `--policy`,
441
+ `--period`, and `--min-period` control that subscription per the
442
+ [Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)'s
443
+ `policy`/`period`/`minPeriod` fields:
444
+
445
+ - **`--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.
446
+ - **`--period`** (seconds, default `60`): the resend interval for `ideal`/`fixed`. Converted to milliseconds on the wire.
447
+ - **`--min-period`** (seconds): fastest allowed transmission rate, only meaningful with `--policy instant`.
448
+
449
+ The protocol also defines a per-path `format` field (`delta`/`full`), but
450
+ this CLI doesn't expose it: signalk-server rejects `full` outright ("Only
451
+ delta format supported, using it") and always sends delta messages
452
+ regardless, so the choice would be misleading.
453
+
454
+ `--subscribe` is separate: it's the connection-level `subscribe` query
455
+ parameter (`none`/`self`/`all`, default `none`), controlling only whether the
456
+ server *additionally* auto-subscribes the connection at its own default
457
+ policy/period — useful with `--subscribe all` to also receive other vessels'
458
+ default-policy updates alongside your explicit subscription.
459
+
460
+ #### Options
461
+
462
+ | Option | Default | Description |
463
+ |---|---|---|
464
+ | `--host` | `$SIGNALK_HOST` | Server base URL. `http://` added if scheme omitted; converted to `ws://`/`wss://` for the stream connection. |
465
+ | `--no-cache` | — | Ignore the cached host |
466
+ | `-c, --context TEXT` | `vessels.self` | SignalK context |
467
+ | `--subscribe [none\|self\|all]` | `none` | Connection-level subscribe policy (SignalK's own `subscribe` query parameter); see above |
468
+ | `--policy [instant\|ideal\|fixed]` | `ideal` | Per-path subscribe `policy` field; see above |
469
+ | `--period SECONDS` | `60` | Per-path subscribe `period` field, in seconds (converted to ms) |
470
+ | `--min-period SECONDS` | — | Per-path subscribe `minPeriod` field, in seconds (converted to ms); only meaningful with `--policy instant` |
471
+ | `--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). |
472
+ | `--no-header` | — | Suppress the CSV header row |
473
+ | `-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`. |
474
+ | `-f, --follow` | — | Keep streaming until interrupted (Ctrl-C) or `--count` is reached. Without this, print the next message then exit. |
475
+ | `-n, --count N` | 1 without `--follow`, unlimited with it | Number of delta messages to output |
476
+ | `--bare` | — | Print to stdout with **no informational messages**. Ideal for piping to other tools. |
477
+
478
+ #### Output formats
479
+
480
+ **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.
481
+
482
+ **raw**: the exact delta message JSON as received from the server, one message per line.
483
+
484
+ **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.
485
+
486
+ #### Examples
487
+
488
+ ```bash
489
+ # Next update for one path, then exit
490
+ python -m signalk_cli.stream deltas --host 10.36.10.21 navigation.speedOverGround
491
+
492
+ # Tail all navigation updates until Ctrl-C
493
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'navigation.*'
494
+
495
+ # Tail oil temperature across every engine (mid-path wildcard)
496
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'propulsion.*.oilTemperature'
497
+
498
+ # Next 20 messages across all subscribed paths, as JSON Lines
499
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --format json --count 20
500
+
501
+ # Tail two specific paths, piping raw deltas to another tool
502
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --format raw --bare \
503
+ navigation.speedOverGround navigation.courseOverGroundTrue
504
+
505
+ # Everything the server has (equivalent to ?subscribe=all), following
506
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --subscribe all --follow
507
+
508
+ # Faster resend interval (5s instead of the 60s default)
509
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --period 5 'navigation.*'
510
+
511
+ # Send every change immediately, no more than 5 times/second
512
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow \
513
+ --policy instant --min-period 0.2 navigation.speedOverGround
514
+
515
+ # Capture 500 messages to an auto-named Feather file
516
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --count 500 --format feather --output 'navigation.*'
517
+
518
+ # Capture until Ctrl-C to a named Feather file
519
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow -o capture.feather 'navigation.*'
520
+ ```
521
+
522
+ ---
523
+
398
524
  ## Time range
399
525
 
400
526
  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).
@@ -1,13 +1,24 @@
1
1
  # SignalK CLI
2
2
 
3
- Query and explore SignalK APIs from the command line, and export data as CSV, Apache Arrow Feather, or JSON.
3
+ 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.
4
4
 
5
5
  APIs supported:
6
6
 
7
- * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html)
7
+ * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html). Commands available:
8
+ - list-paths
9
+ - list-providers
10
+ - list-contexts
11
+ - query
12
+ - cardinality
13
+ * [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html). Commands available:
14
+ - deltas
8
15
 
9
16
  ## Installation
10
17
 
18
+ `signalk-cli` is published to PyPi at https://pypi.org/project/signalk-cli/
19
+
20
+ 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.
21
+
11
22
  ### PyPi
12
23
 
13
24
  ```pip install signalk-cli``` or ```uv pip install signalk-cli```
@@ -34,12 +45,11 @@ uv run --with signalk-cli signalk_cli.history list-providers
34
45
 
35
46
  ## Running
36
47
 
37
- Run via `python -m signalk_cli.history <command>`.
48
+ 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>`.
38
49
 
39
50
  ## Determining SignalK host name
40
51
 
41
- 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
42
- the host using mDNS (aka Bonjour) and locally cached (see [Default Caching](#default-caching)).
52
+ 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)).
43
53
 
44
54
  ```bash
45
55
  export SIGNALK_HOST=192.168.6.99 # http:// is added automatically if omitted
@@ -367,6 +377,122 @@ python -m signalk_cli.history list-contexts --host 10.36.10.21 --format raw --ba
367
377
 
368
378
  ---
369
379
 
380
+ ## SignalK v1 Streaming API
381
+
382
+ The `signalk_cli.stream` module connects to a running SignalK server's live delta
383
+ WebSocket feed (as opposed to `signalk_cli.history`, which queries recorded history).
384
+
385
+ ### `deltas`
386
+
387
+ Stream live delta updates from the SignalK v1 Streaming API.
388
+
389
+ ```
390
+ python -m signalk_cli.stream deltas [OPTIONS] [PATH...]
391
+ ```
392
+
393
+ By default, prints the next delta message and exits — useful for a quick check.
394
+ Use `--follow` to keep tailing until interrupted with Ctrl-C, optionally capped
395
+ with `--count`.
396
+
397
+ **PATH** arguments are sent verbatim to the server as an explicit subscription,
398
+ one per path. They may be literal SignalK paths (e.g. `navigation.speedOverGround`)
399
+ or use the SignalK subscription wildcard `*`, matched server-side per the
400
+ [Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)
401
+ — unlike `history`'s PATH patterns, which the client resolves by matching
402
+ against the server's enumerated `/paths` list:
403
+
404
+ - `*` at the end of a path matches any suffix, e.g. `navigation.*`
405
+ - `*` as a middle segment matches any single segment there, e.g. `propulsion.*.oilTemperature`
406
+ - a bare `*` subscribes to every path in the context
407
+
408
+ Quote wildcarded paths (e.g. `'navigation.*'`) so the shell doesn't expand them
409
+ against local filenames first.
410
+
411
+ `deltas` always sends its own explicit subscribe message for `--context`,
412
+ covering PATH arguments if given, otherwise every path (`*`). `--policy`,
413
+ `--period`, and `--min-period` control that subscription per the
414
+ [Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)'s
415
+ `policy`/`period`/`minPeriod` fields:
416
+
417
+ - **`--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.
418
+ - **`--period`** (seconds, default `60`): the resend interval for `ideal`/`fixed`. Converted to milliseconds on the wire.
419
+ - **`--min-period`** (seconds): fastest allowed transmission rate, only meaningful with `--policy instant`.
420
+
421
+ The protocol also defines a per-path `format` field (`delta`/`full`), but
422
+ this CLI doesn't expose it: signalk-server rejects `full` outright ("Only
423
+ delta format supported, using it") and always sends delta messages
424
+ regardless, so the choice would be misleading.
425
+
426
+ `--subscribe` is separate: it's the connection-level `subscribe` query
427
+ parameter (`none`/`self`/`all`, default `none`), controlling only whether the
428
+ server *additionally* auto-subscribes the connection at its own default
429
+ policy/period — useful with `--subscribe all` to also receive other vessels'
430
+ default-policy updates alongside your explicit subscription.
431
+
432
+ #### Options
433
+
434
+ | Option | Default | Description |
435
+ |---|---|---|
436
+ | `--host` | `$SIGNALK_HOST` | Server base URL. `http://` added if scheme omitted; converted to `ws://`/`wss://` for the stream connection. |
437
+ | `--no-cache` | — | Ignore the cached host |
438
+ | `-c, --context TEXT` | `vessels.self` | SignalK context |
439
+ | `--subscribe [none\|self\|all]` | `none` | Connection-level subscribe policy (SignalK's own `subscribe` query parameter); see above |
440
+ | `--policy [instant\|ideal\|fixed]` | `ideal` | Per-path subscribe `policy` field; see above |
441
+ | `--period SECONDS` | `60` | Per-path subscribe `period` field, in seconds (converted to ms) |
442
+ | `--min-period SECONDS` | — | Per-path subscribe `minPeriod` field, in seconds (converted to ms); only meaningful with `--policy instant` |
443
+ | `--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). |
444
+ | `--no-header` | — | Suppress the CSV header row |
445
+ | `-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`. |
446
+ | `-f, --follow` | — | Keep streaming until interrupted (Ctrl-C) or `--count` is reached. Without this, print the next message then exit. |
447
+ | `-n, --count N` | 1 without `--follow`, unlimited with it | Number of delta messages to output |
448
+ | `--bare` | — | Print to stdout with **no informational messages**. Ideal for piping to other tools. |
449
+
450
+ #### Output formats
451
+
452
+ **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.
453
+
454
+ **raw**: the exact delta message JSON as received from the server, one message per line.
455
+
456
+ **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.
457
+
458
+ #### Examples
459
+
460
+ ```bash
461
+ # Next update for one path, then exit
462
+ python -m signalk_cli.stream deltas --host 10.36.10.21 navigation.speedOverGround
463
+
464
+ # Tail all navigation updates until Ctrl-C
465
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'navigation.*'
466
+
467
+ # Tail oil temperature across every engine (mid-path wildcard)
468
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'propulsion.*.oilTemperature'
469
+
470
+ # Next 20 messages across all subscribed paths, as JSON Lines
471
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --format json --count 20
472
+
473
+ # Tail two specific paths, piping raw deltas to another tool
474
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --format raw --bare \
475
+ navigation.speedOverGround navigation.courseOverGroundTrue
476
+
477
+ # Everything the server has (equivalent to ?subscribe=all), following
478
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --subscribe all --follow
479
+
480
+ # Faster resend interval (5s instead of the 60s default)
481
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --period 5 'navigation.*'
482
+
483
+ # Send every change immediately, no more than 5 times/second
484
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow \
485
+ --policy instant --min-period 0.2 navigation.speedOverGround
486
+
487
+ # Capture 500 messages to an auto-named Feather file
488
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --count 500 --format feather --output 'navigation.*'
489
+
490
+ # Capture until Ctrl-C to a named Feather file
491
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow -o capture.feather 'navigation.*'
492
+ ```
493
+
494
+ ---
495
+
370
496
  ## Time range
371
497
 
372
498
  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).
@@ -0,0 +1,114 @@
1
+ [project]
2
+ name = "signalk-cli"
3
+ version = "2.0.1"
4
+ description = "Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "Apache-2.0"
8
+ keywords = [
9
+ "signalk",
10
+ "sailing",
11
+ "marine",
12
+ "nmea",
13
+ "boating",
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Natural Language :: English",
19
+ "Intended Audience :: Developers",
20
+ "Topic :: Scientific/Engineering",
21
+ "Programming Language :: Python",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ ]
25
+ dependencies = [
26
+ "click>=8.3.3",
27
+ "niquests[ws]>=2.28",
28
+ "zeroconf>=0.149.16,<0.150.0",
29
+ ]
30
+
31
+ [[project.authors]]
32
+ name = "jey burrows"
33
+ email = "jrb@rhizomatics.org.uk"
34
+
35
+ [project.optional-dependencies]
36
+ feather = ["pyarrow>=17.0"]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/rhizomatics/signalk-cli"
40
+ Repository = "https://github.com/rhizomatics/signalk-cli"
41
+ Issues = "https://github.com/rhizomatics/signalk-cli/issues"
42
+ Changelog = "https://github.com/rhizomatics/signalk-cli/blob/main/CHANGELOG.md"
43
+
44
+ [project.scripts]
45
+ "signalk_cli.history" = "signalk_cli.history.cli:cli"
46
+ "signalk_cli.stream" = "signalk_cli.stream.cli:cli"
47
+
48
+ [dependency-groups]
49
+ dev = [
50
+ "ruff>=0.16.0",
51
+ "pre-commit>=4.4.0",
52
+ "pytest>=9.0.0",
53
+ "pytest-mock>=3.15.0",
54
+ "pytest-cov>=7.0.0",
55
+ "pytest-xdist",
56
+ "mypy",
57
+ "coverage",
58
+ "icdiff",
59
+ "codespell",
60
+ ]
61
+
62
+ [tool.bandit]
63
+ exclude_dirs = ["tests"]
64
+
65
+ [tool.ruff.format]
66
+ quote-style = "double"
67
+ indent-style = "space"
68
+ skip-magic-trailing-comma = false
69
+ line-ending = "auto"
70
+
71
+ [tool.pytest.ini_options]
72
+ pythonpath = [
73
+ "src",
74
+ ".",
75
+ ]
76
+ asyncio_mode = "auto"
77
+ testpaths = ["tests"]
78
+ norecursedirs = [".git"]
79
+
80
+ [tool.coverage.run]
81
+ branch = true
82
+ source = ["src"]
83
+
84
+ [tool.coverage.report]
85
+ fail_under = 0
86
+
87
+ [tool.coverage.xml]
88
+ output = "cov.xml"
89
+
90
+ [tool.coverage.html]
91
+ directory = "htmlcov"
92
+
93
+ [tool.codespell]
94
+ skip = ".venv,.git,htmlcov,site,./.*,*.csv,*.json,README.*.md,support"
95
+ count = true
96
+ quiet-level = 2
97
+ ignore-words-list = "hass,referer,uint"
98
+
99
+ [tool.mypy]
100
+ mypy_path = ["./src"]
101
+
102
+ [tool.uv]
103
+ compile-bytecode = true
104
+ managed = true
105
+ exclude-newer = "5 days"
106
+ add-bounds = "major"
107
+
108
+ [tool.uv.build-backend]
109
+ module-root = "src"
110
+ module-name = "signalk_cli"
111
+
112
+ [build-system]
113
+ requires = ["uv_build>=0.11.13,<0.12.0"]
114
+ build-backend = "uv_build"
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "signalk-cli"
3
- version = "1.2.0"
4
- description = "Query the SignalK v2 History API and export data as CSV or Apache Arrow Feather"
3
+ version = "2.0.1"
4
+ description = "Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  license="Apache-2.0"
@@ -23,7 +23,7 @@ classifiers = [
23
23
 
24
24
  dependencies = [
25
25
  "click>=8.3.3",
26
- "niquests>=2.28",
26
+ "niquests[ws]>=2.28",
27
27
  "zeroconf>=0.149.16,<0.150.0",
28
28
  ]
29
29
 
@@ -31,7 +31,7 @@ dependencies = [
31
31
  feather = ["pyarrow>=17.0"]
32
32
  [dependency-groups]
33
33
  dev = [
34
- "ruff>=0.15.0",
34
+ "ruff>=0.16.0",
35
35
  "pre-commit>=4.4.0",
36
36
  "pytest>=9.0.0",
37
37
  "pytest-mock>=3.15.0",
@@ -115,3 +115,4 @@ module-name = "signalk_cli"
115
115
 
116
116
  [project.scripts]
117
117
  "signalk_cli.history" = "signalk_cli.history.cli:cli"
118
+ "signalk_cli.stream" = "signalk_cli.stream.cli:cli"
@@ -2,6 +2,8 @@
2
2
 
3
3
  print("Supported SignalK APIs:")
4
4
  print(" signalk_cli.history — SignalK v2 History API")
5
+ print(" signalk_cli.stream — SignalK v1 Streaming (delta) API")
5
6
  print()
6
7
  print("Usage: python -m signalk_cli.<api> [COMMAND] [OPTIONS]")
7
8
  print(" python -m signalk_cli.history --help")
9
+ print(" python -m signalk_cli.stream --help")
@@ -1,35 +1,30 @@
1
1
  """Click CLI for the SignalK v2 History API."""
2
2
 
3
- import contextlib
4
3
  import csv
5
- import io
6
4
  import json
7
5
  import re
8
6
  import sys
9
- from datetime import datetime, timezone
7
+ from datetime import UTC, datetime
10
8
  from pathlib import Path
11
9
  from urllib.parse import urlparse
12
10
 
13
11
  import click
14
12
  import niquests
15
13
 
14
+ from ..net import bare_option, host_option, resolve_host, stderr_ctx
16
15
  from .history_api import (
17
16
  HISTORY_BASE,
18
17
  api_error,
19
18
  apply_time_default,
20
- discover_host,
21
19
  expand_paths,
22
20
  fetch_server_paths,
23
- get_cached_host,
24
21
  normalise_duration,
25
- normalise_host,
26
22
  resolve_provider,
27
- save_cached_host,
28
23
  )
29
24
  from .output import (
25
+ _POSITION_RE,
30
26
  CARDINALITY_COLUMNS,
31
27
  FEATHER_EXTENSIONS,
32
- _POSITION_RE,
33
28
  compute_cardinality,
34
29
  write_csv,
35
30
  write_csv_wide,
@@ -72,35 +67,8 @@ def _list_fmt_callback(ctx, param, value):
72
67
  raise click.BadParameter(f"'{value}' is not one of 'csv', 'json', 'raw'")
73
68
 
74
69
 
75
- def _host_option(f):
76
- return click.option(
77
- "--host",
78
- default=None,
79
- envvar="SIGNALK_HOST",
80
- help="SignalK server base URL. http:// added if scheme omitted. "
81
- "Discovered via mDNS if omitted.",
82
- )(f)
83
-
84
-
85
- def _resolve_host(host: str | None, no_cache: bool = False) -> str:
86
- """Return a normalised host URL, discovering via mDNS if none provided."""
87
- if host:
88
- return normalise_host(host)
89
- if not no_cache:
90
- cached = get_cached_host()
91
- if cached:
92
- click.echo(f"Using cached host: {cached}", err=True)
93
- return cached
94
- click.echo("No host specified — searching for SignalK via mDNS...", err=True)
95
- discovered = discover_host()
96
- if not discovered:
97
- raise click.UsageError(
98
- "No SignalK server found via mDNS. Use --host or set SIGNALK_HOST."
99
- )
100
- click.echo(f"Discovered: {discovered}", err=True)
101
- if not no_cache:
102
- save_cached_host(discovered)
103
- return discovered
70
+ _host_option = host_option
71
+ _resolve_host = resolve_host
104
72
 
105
73
 
106
74
  def _provider_options(f):
@@ -127,18 +95,8 @@ def _time_options(f):
127
95
  return f
128
96
 
129
97
 
130
- def _bare_option(f):
131
- return click.option(
132
- "--bare",
133
- is_flag=True,
134
- help="Suppress all informational messages, outputting data only.",
135
- )(f)
136
-
137
-
138
- def _stderr_ctx(bare: bool) -> contextlib.AbstractContextManager:
139
- return (
140
- contextlib.redirect_stderr(io.StringIO()) if bare else contextlib.nullcontext()
141
- )
98
+ _bare_option = bare_option
99
+ _stderr_ctx = stderr_ctx
142
100
 
143
101
 
144
102
  def _build_time_params(from_: str | None, to: str | None, duration: str | None) -> dict:
@@ -341,7 +299,7 @@ def query(
341
299
  # Generate auto-named file path now that format is known
342
300
  if auto_name:
343
301
  server_name = urlparse(host).hostname or re.sub(r"[^\w.-]", "_", host)
344
- ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
302
+ ts = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
345
303
  ext = (
346
304
  ".feather"
347
305
  if fmt == "feather"