Skip to main content

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 failed check — 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 --car "Audi Q8"          # or --car <car_id>, for a specific car

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

Cars:
  ✓ Audi Q8              ONLINE   1s ago
  ⚠ BMW F36              STALE    18s ago
  ✗ Запор                OFFLINE  never

All systems healthy.

Exits 0 when healthy, 1 on any failed check. 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     │ Car ID                           │ Device           │ Last seen │
├─────────┼──────────┼──────────────────────────────────┼──────────────────┼───────────┤
│ ONLINE  │ Audi Q8  │ a1491b52-0db3-4649-acba-...      │ 89de0a7570734f07 │ 2s ago    │
│ STALE   │ BMW F36  │ 4db27641-09a5-4fc3-bc03-...      │ ed12bab94fccf357 │ 18s ago   │
│ OFFLINE │ Запор    │ —                                │ (unassigned)     │ never     │
└─────────┴──────────┴──────────────────────────────────┴──────────────────┴───────────┘

How it works

  • login POSTs /api/auth/login and saves the JWT to ~/.config/carstream/credentials.json (chmod 0600).
  • All authenticated GETs send Authorization: Bearer <jwt>. A 401 prints "Session expired. Run cscli login again." and exits non-zero.
  • Streaming subcommands (dashboard, watch, tail) open wss://<server>/ws/telemetry/<car_id>?token=<jwt> and merge incoming partial frames into one running snapshot — telemetry/fast carries rpm/speed/lat/lon, telemetry/slow carries coolant_temp/oil_temp/fuel_level, status carries 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 plotext for a real two-axis line chart (RPM left, Speed right). plotext writes ANSI escapes; rich.Text.from_ansi parses 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 login again.
  • File at ~/.config/carstream/credentials.json is chmod 0600; don't commit it anywhere.
  • Self-hosted server? cscli login --server https://your-host and 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 cars to pass via --car <name|id> (a car name is matched case-insensitively). With exactly one paired car, --car is optional.

Dependencies

httpx (HTTP), websockets (WS streaming), rich (TUI), plotext (terminal line chart), pyfiglet (giant ASCII text for watch). Declared with lower bounds 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):

  1. Go to https://pypi.org/manage/account/publishing/
  2. Add a Pending publisher for carstream-cli:
    • Owner: ypankovych
    • Repository: car-telemetry
    • Workflow: publish-cli.yml
    • Environment: pypi
  3. 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
#    (keep them in sync — the workflow verifies the tag against
#    pyproject.toml only)

# 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.

Release files for carstream-cli 0.9.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for carstream-cli 0.9.1
File Size Uploaded
carstream_cli-0.9.1.tar.gz 16.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for carstream-cli 0.9.1
File Interpreter ABI Platform
carstream_cli-0.9.1-py3-none-any.whl Python 3 none any Details

Total release size: 36.0 kB

Release files / carstream_cli-0.9.1.tar.gz

Download URL carstream_cli-0.9.1.tar.gz
Size 16.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d1447c65cdf92b252f0f41349ef884654da7eb39133295e70878afb9c4139175
BLAKE2b-256 checksum
How to use checksums
36282cd84fc3c2247d36df01b75066fc3fb6914801ac280c2577fb78774abe41
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / carstream_cli-0.9.1-py3-none-any.whl

Download URL carstream_cli-0.9.1-py3-none-any.whl
Size 19.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f5b5825a598a5deda7c2c645aa208ccc07fd9c2d3385452059f46d2cbbff1040
BLAKE2b-256 checksum
How to use checksums
46ad7549b25f7ae9348e0641bc9d898926114dadea2d24c7139d691769ff62d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.9.1 This release

2 release files

0.9.0

2 release files

0.8.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page