Metadata-Version: 2.4
Name: carstream-cli
Version: 0.9.0
Summary: Terminal client for CarStream — live ASCII telemetry dashboard, sparklines, RPM/Speed trend chart, raw JSON streaming, end-to-end health check.
Project-URL: Homepage, https://carstream.live
Project-URL: Repository, https://github.com/ypankovych/car-telemetry
Project-URL: Issues, https://github.com/ypankovych/car-telemetry/issues
Author-email: Yaroslav Pankovych <flower.moor@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: carstream,cli,dashboard,obd,raspberry-pi,telemetry,tui
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: Terminals
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: plotext>=5.3
Requires-Dist: pyfiglet>=1.0
Requires-Dist: rich>=13
Requires-Dist: websockets>=12
Description-Content-Type: text/markdown

# 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

```bash
# 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

```bash
cscli login                              # defaults to https://carstream.live
cscli login --server http://localhost:8000 --email me@example.com
cscli logout
```

### Live ASCII dashboard

```bash
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

```bash
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

```bash
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

```bash
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` if all green, `1` on any red. Pipe-friendly for cron monitoring:

```bash
*/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

```bash
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`). 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):

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:**

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