Terminal client for CarStream — live ASCII telemetry dashboard, sparklines, RPM/Speed trend chart, raw JSON streaming, end-to-end health check.
Project description
cscli — CarStream terminal client
A small Python CLI that talks to your CarStream server over HTTPS + WebSocket. Live ASCII gauges, sparklines, RPM/Speed trend chart, single-field giant readouts, raw JSON streaming, and a one-shot health check — all from the terminal.
┌──── CarStream — Audi Q8 ──────────────────────────────────────────────┐
│ RPM ████████████████░░░░░░░░░░░░░░ 3420 rpm ▁▂▄▆█▆▄▃▂▁▂▃▅▇█│
│ Speed ██████████░░░░░░░░░░░░░░░░░░░░ 72 km/h ▁▁▂▃▄▆▇█▇▆▅▄▃▂▁│
│ Throttle ███████░░░░░░░░░░░░░░░░░░░░░░░░ 23 % ▁▁▂▂▃▃▄▅▆▆▆▅▄▃▁│
│ Load ████░░░░░░░░░░░░░░░░░░░░░░░░░░ 14 % ▁▁▁▂▂▃▃▄▄▄▃▃▂▁▁│
│ Coolant ███████████████████░░░░░░░░░░░ 91 °C ▆▆▆▇▇▇▇█████▇▇▆│
│ Oil ███████████████████░░░░░░░░░░░ 97 °C ▅▅▆▆▆▇▇▇▇█████▇│
│ Fuel ████████████████░░░░░░░░░░░░░░ 56 % █████▇▇▇▇▆▆▆▅▅▅│
└────────────────────────────────────────────────────────────────────────┘
┌──── Trend (last ~60 frames) ──────────────────────────────────────────┐
│ 6000┤ RPM ─ ╭─╮ │
│ 4000┤ Speed ─ ╭───╮ ╭───╯ ╰─ │
│ 2000┤ ╭─────────╯ ╰──────────────────╯ │
│ 0┴────────────────────────────────────────────────────────── │
└────────────────────────────────────────────────────────────────────────┘
┌──── Status ───────────────────────────────────────────────────────────┐
│ Car: Audi Q8 │
│ Status: ONLINE │
│ GPS: 48.91968, 24.70627 │
│ Maps: Open in Google Maps ↗ │
│ Frame: 0.2s ago │
└────────────────────────────────────────────────────────────────────────┘
Install
# from PyPI
pipx install carstream-cli
# or from a local checkout
cd cli && pipx install .
# or in a regular venv
python -m venv .venv && source .venv/bin/activate
pip install carstream-cli
Python ≥ 3.10.
Subcommands
| Command | What it does |
|---|---|
cscli login |
Email + password prompt. JWT cached at ~/.config/carstream/credentials.json (chmod 0600). |
cscli logout |
Forget cached credentials. |
cscli devices |
Table of cars with ONLINE / STALE / OFFLINE pill, friendly last-seen ("12s ago"). |
cscli status |
End-to-end health check: server reachable, auth valid, WS upgrades, per-Pi liveness. Exits non-zero on any red — cron-friendly. |
cscli dashboard |
Live ASCII gauges + sparklines + plotext RPM/Speed trend chart + status panel with online pill. |
cscli watch <field> |
Single-field giant ASCII readout via pyfiglet, with sparkline + frame-age. Eg cscli watch speed. |
cscli tail |
Stream raw frames as JSON-per-line to stdout. Pipe-friendly: cscli tail | jq 'select(.speed > 100)'. |
Usage
Auth
cscli login # defaults to https://carstream.live
cscli login --server http://localhost:8000 --email me@example.com
cscli logout
Live ASCII dashboard
cscli dashboard # auto-picks if you have one car
cscli dashboard --device <device_id> # for a specific device
What you see:
- Gauge cluster with bar + color-coded value + 60-sample sparkline per metric
- RPM/Speed trend chart over the same window (plotext, two y-axes)
- Status panel: car name, ONLINE / STALE / OFFLINE pill (5s / 30s thresholds, matches the web UI), GPS, frame age, and an OSC-8
Open in Google Maps ↗link to the current point.
The Status panel pre-fetches /api/telemetry/<id>/last_position on startup so the GPS row + Maps link are populated immediately, even before the first WebSocket frame. While showing the parked position the GPS line is suffixed with (last known) in yellow and a Parked: <ISO timestamp> row appears below; both go away as soon as a live GPS-bearing frame arrives.
The Maps link uses OSC-8 hyperlinks — clickable in iTerm2, modern xterm, gnome-terminal, wezterm, kitty; plain text on terminals that don't support it.
Ctrl-C exits cleanly.
Watching a single field
cscli watch speed # giant ASCII number, sparkline below
cscli watch rpm --font slant
cscli watch fuel_level --font doom
Useful for putting on a secondary monitor or a Pi Zero in the kitchen.
Streaming raw frames
cscli tail # one JSON object per line, raw
cscli tail --pretty # pretty-printed JSON
cscli tail --field rpm # only frames carrying that field; output is {field, ts}
# Pipe to your own tools:
cscli tail | jq 'select(.speed > 100)'
cscli tail --field rpm | awk -F'"rpm":' '{print $2}'
cscli tail --field fuel_level | jq -r .fuel_level | datamash mean 1
The hot path skips JSON parsing entirely and writes the raw WS frame straight to stdout — your downstream consumer can re-parse if needed.
Health check
cscli status
Server health · https://carstream.live
✓ Credentials present (flower.moor@gmail.com)
✓ Server reachable (200 in 84 ms)
✓ Auth valid (3 cars)
✓ WebSocket upgrades successfully
Devices:
✓ Audi Q8 ONLINE 1s ago
⚠ BMW F36 STALE 18s ago
✗ Запор OFFLINE never
All systems healthy.
Exits 0 if all green, 1 on any red. Pipe-friendly for cron monitoring:
*/5 * * * * cscli status >/dev/null 2>&1 || \
curl -X POST <slack-webhook> -d '{"text":"CarStream unhealthy"}'
Offline devices intentionally don't flag a failure (parked car is fine); only network/auth/handshake errors do.
Listing cars
cscli devices
┌─────────┬──────────┬──────────────────────────────────┬──────────────┐
│ Status │ Name │ Device ID │ Last seen │
├─────────┼──────────┼──────────────────────────────────┼──────────────┤
│ ONLINE │ Audi Q8 │ a1491b52-0db3-4649-acba-... │ 2s ago │
│ STALE │ BMW F36 │ 4db27641-09a5-4fc3-bc03-... │ 18s ago │
│ OFFLINE │ Запор │ (unassigned) │ never │
└─────────┴──────────┴──────────────────────────────────┴──────────────┘
How it works
loginPOSTs/api/auth/loginand saves the JWT to~/.config/carstream/credentials.json(chmod 0600).- All authenticated GETs send
Authorization: Bearer <jwt>. A 401 prints "Session expired. Runcscli loginagain." and exits non-zero. - Streaming subcommands (
dashboard,watch,tail) openwss://<server>/ws/telemetry/<device_id>?token=<jwt>and merge incoming partial frames into one running snapshot —telemetry/fastcarriesrpm/speed/lat/lon,telemetry/slowcarriescoolant_temp/oil_temp/fuel_level,statuscarries the heartbeat. - The dashboard buffer is 60 samples per gauge (~6 s at 10 Hz fast frames or
~60 s at 1 Hz slow frames) and renders unicode block sparklines (
▁▂▃▄▅▆▇█) auto-scaled to the buffer's min/max. - The trend panel uses
plotextfor a real two-axis line chart (RPM left, Speed right). plotext writes ANSI escapes;rich.Text.from_ansiparses them so the colors integrate with the surrounding panel borders. - The Status panel re-renders once a second so the ONLINE / STALE / OFFLINE pill ages correctly even when no frames arrive.
Auth notes
- Tokens are JWTs from the same backend the web/mobile apps use. They expire
(typically 24 h); if a request 401s the CLI prompts you to
cscli loginagain. - File at
~/.config/carstream/credentials.jsonischmod 0600; don't commit it anywhere. - Self-hosted server?
cscli login --server https://your-hostand you're done.
Multiple cars
When the user has more than one paired car, every streaming subcommand
(dashboard, watch, tail) refuses to auto-pick and lists the device IDs
to pass via --device <id>. With exactly one paired car, --device is
optional.
Dependencies
httpx (HTTP), websockets (WS streaming), rich (TUI), plotext (terminal
line chart), pyfiglet (giant ASCII text for watch). Pinned in
pyproject.toml.
Internal layout
cli/carstream_cli/
├── __init__.py
├── __main__.py subcommand dispatcher (argparse)
├── auth.py credentials file r/w (chmod 0600)
├── client.py httpx wrappers + ws_url builder
├── dashboard.py multi-panel live dashboard
└── util.py shared helpers — sparkline, frame_field,
safe_loads, human_age, online_label/text
util.py is the single source of truth for: WS frame field extraction
(top-level vs the JSONB data blob), JSON-with-fallback parsing, sparkline
rendering, ISO timestamp parsing, the 5 s / 30 s ONLINE/STALE/OFFLINE
thresholds. Add a new subcommand by importing from there instead of
re-implementing.
Releasing
CI publishes to PyPI via Trusted Publishing on tag push. The workflow
lives at .github/workflows/publish-cli.yml.
One-time setup (PyPI side):
- Go to https://pypi.org/manage/account/publishing/
- Add a Pending publisher for
carstream-cli:- Owner:
ypankovych - Repository:
car-telemetry - Workflow:
publish-cli.yml - Environment:
pypi
- Owner:
- On GitHub, create an Environment named
pypi(Settings → Environments → New). Optional: protect it with a manual approval rule.
Cutting a release:
# 1. Bump version in cli/pyproject.toml AND cli/carstream_cli/__init__.py
# (they must match — the workflow verifies)
# 2. Commit + tag with the matching prefix
git add cli/pyproject.toml cli/carstream_cli/__init__.py
git commit -m "cli: release 0.X.Y"
git tag cli-v0.X.Y
git push origin main --tags
The cli-v* tag triggers publish-cli.yml → builds sdist + wheel →
uploads to PyPI as carstream-cli. skip-existing: true makes re-runs
safe if the same version already exists. Manual republish is also
available via Actions → "Publish cscli to PyPI" → Run workflow.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file carstream_cli-0.8.2.tar.gz.
File metadata
- Download URL: carstream_cli-0.8.2.tar.gz
- Upload date:
- Size: 15.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d11ca171265d415115ea05b22e9dee417b469941b7ea757d8839788cbe8be272
|
|
| MD5 |
7f21bbf52184550e36b560101cab42a6
|
|
| BLAKE2b-256 |
8390f90e45a1ccf40b30ee7e645094108cbc06e2f2aeb2d593a3a1172a8e8473
|
Provenance
The following attestation bundles were made for carstream_cli-0.8.2.tar.gz:
Publisher:
publish-cli.yml on ypankovych/car-telemetry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
carstream_cli-0.8.2.tar.gz -
Subject digest:
d11ca171265d415115ea05b22e9dee417b469941b7ea757d8839788cbe8be272 - Sigstore transparency entry: 1435454962
- Sigstore integration time:
-
Permalink:
ypankovych/car-telemetry@757d80937bf4dc301847224948bc6b56693fe694 -
Branch / Tag:
refs/tags/cli-v0.8.2 - Owner: https://github.com/ypankovych
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-cli.yml@757d80937bf4dc301847224948bc6b56693fe694 -
Trigger Event:
push
-
Statement type:
File details
Details for the file carstream_cli-0.8.2-py3-none-any.whl.
File metadata
- Download URL: carstream_cli-0.8.2-py3-none-any.whl
- Upload date:
- Size: 19.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
faf73f4584cedea68b1b37563a0fcaca2a252968ff1774ca500b1f38475fb893
|
|
| MD5 |
e05f725cbc818bc64f28f97708ee9733
|
|
| BLAKE2b-256 |
16ab51f8bdb193d6c841209ea7581ea85da15f727a5f61fbc34ce2f6f0f83ab8
|
Provenance
The following attestation bundles were made for carstream_cli-0.8.2-py3-none-any.whl:
Publisher:
publish-cli.yml on ypankovych/car-telemetry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
carstream_cli-0.8.2-py3-none-any.whl -
Subject digest:
faf73f4584cedea68b1b37563a0fcaca2a252968ff1774ca500b1f38475fb893 - Sigstore transparency entry: 1435455005
- Sigstore integration time:
-
Permalink:
ypankovych/car-telemetry@757d80937bf4dc301847224948bc6b56693fe694 -
Branch / Tag:
refs/tags/cli-v0.8.2 - Owner: https://github.com/ypankovych
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-cli.yml@757d80937bf4dc301847224948bc6b56693fe694 -
Trigger Event:
push
-
Statement type: