Metadata-Version: 2.4
Name: qapu-cli
Version: 0.3.1
Summary: CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm.
Author: OVOO Technology
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: typer<1.0,>=0.12
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: rich<16.0,>=13.0

# Qapu CLI

A thin command-line client for the Qapu API (`api.ovoo.com.tr`), built for the Hermes agent (runs outside this Swarm, in a separate datacenter, and only ever talks to Qapu through this public API) - but usable by anything else that needs to script against Qapu from outside the private network.

**Status: early development.** Gated by a temporary shared-secret header, not real auth yet - see "Auth (current placeholder)" below before using this against production.

## Why a separate `tools/` project, not a `services/`

This isn't a deployed backend service - it's a distributable client tool, installed wherever Hermes (or anyone else) runs. Kept in the same monorepo (per `CLAUDE.md`'s ADR-0001 - one repo, low context-switching for a 2-person team) rather than its own repo, since it's small and needs to stay in sync with the API it calls.

## Install

**Anyone with GitHub access to this (private) repo - no local clone needed**, `pip` installs straight from the `tools/cli` subdirectory over git:

```bash
pip install "git+ssh://git@github.com/ovoo-tech/qapu.git#subdirectory=tools/cli"
```

(needs an SSH key already authorized on your GitHub account for this repo - the usual case for anyone on the team. No SSH key set up? Use an HTTPS Personal Access Token instead: `pip install "git+https://<PAT>@github.com/ovoo-tech/qapu.git#subdirectory=tools/cli"`.)

**Working on the CLI itself** (this repo already checked out) - editable install so local edits take effect immediately:

```bash
cd tools/cli
pip install -e .
```

Either way installs a `qapu` command (see `pyproject.toml`'s `[project.scripts]`). This package has no dependency on the rest of the monorepo (`qapu_common` etc.) - it only ever talks to Qapu over HTTP, never imports it directly - which is exactly what makes the git-subdirectory install above work without cloning anything else.

## Configuration

| Env var | Purpose |
|---|---|
| `QAPU_API_URL` | Base URL. Defaults to `https://api.ovoo.com.tr`. Point at `http://localhost:8000` or an internal IP for local/dev testing. |
| `QAPU_HERMES_KEY` | Shared-secret value for the `X-Hermes-Key` header - see "Auth" below. Required for every command except `qapu health`. |

## Commands

```bash
qapu --version                       # print the installed qapu-cli version and exit
qapu health                          # GET /health - no auth, quick connectivity check
qapu device list                     # GET /hermes/devices - every device, bulk, as a table
qapu device list --json              # same data, raw JSON
qapu device list --status online     # only devices whose Update_Time moved in the last 30 min (see ONLINE_THRESHOLD_MINUTES in main.py - there's no real online/offline field, this is a heuristic)
qapu device list --status offline
qapu device list --model B107AA_R5   # case-insensitive substring match on Hardware.Model.Name
qapu device list --limit 100
qapu device get <device_id>          # GET /hermes/devices/{device_id} - one device, human-readable summary
qapu device get <device_id> --json   # same data, raw JSON
qapu variable list                   # GET /hermes/variables - full variable catalog, as a table
qapu variable list --json            # same data, raw JSON
qapu variable list --segment energy  # filter by segment name (server-side)
qapu variable list --search vrms     # substring match on ID or description (client-side)
qapu variable get <variable_id>      # GET /hermes/variables/{variable_id} - one variable, human-readable summary
qapu variable get <variable_id> --json
qapu data <device_id>                # GET /hermes/data/{device_id} - latest value + timestamp per variable
qapu data <device_id> --json
qapu data <device_id> --energy       # only Energy segment variables
qapu data <device_id> --gsm          # only GSM segment variables
qapu data <device_id> --voltage      # only voltage variables (Unit == V, includes battery voltage)
qapu data <device_id> --current      # only current variables (Unit == A)
qapu data <device_id> --battery      # only battery variables (Variable ID starting with B_)
qapu data <device_id> --search vrms  # substring match on variable ID, e.g. VRMS_R/S/T only
```

`qapu data`'s family filters (`--energy`/`--gsm`/`--voltage`/`--current`/`--battery`) are a union - passing more than one shows variables matching any of them. `--search` narrows whatever they leave (or the full list, if none were given) further, by a case-insensitive substring match on the variable ID - the way to pin down an exact family like `VRMS_R`/`VRMS_S`/`VRMS_T`. Values come from the measurement cache's rolling buffer (the same source `/measurement/{device_id}/last/{variable_id}` reads from), not a fresh device poll - "latest" means the most recent packet already ingested, not real-time.

Filtering (`--status`/`--model`/`--limit`) happens client-side in the CLI, not on the server - fine at the current fleet size, worth moving server-side (`GET /hermes/devices?status=...`) if it ever grows large enough to matter.

## Auth (current placeholder - read this before pointing at production)

`api.ovoo.com.tr` is genuinely public on the internet. The Hermes endpoints (`services/api/src/routers/hermes.py`) are gated by `require_hermes_key` (`services/api/src/dependencies.py`) - a single shared-secret string compared against the `X-Hermes-Key` header, checked via the `HERMES_SHARED_SECRET` env var on the API side. This is **deliberately temporary**: it exists only so the CLI/API plumbing could be built and tested end-to-end before the real auth design was ready, not because a shared secret is considered good enough long-term.

**Real plan** (not built yet - phase 2, along with the score/comment table Hermes will eventually write to): an admin-role `hermes-qapu` account in the `users` table, with the CLI gaining a `qapu login` command that authenticates through the existing `JWT_Auth` flow every other Qapu client already uses, storing a short-lived token instead of a static shared secret. `client.py` is written so only it needs to change when that lands - nothing in `main.py` should need to know how auth works under the hood.

Until then: `HERMES_SHARED_SECRET` fails closed (unset = every Hermes request rejected, never silently open), but a leaked shared-secret string is a much blunter credential than a scoped, revocable JWT - don't treat this as production-grade access control.

## Running locally

```bash
QAPU_API_URL=http://localhost:8000 QAPU_HERMES_KEY=dev-secret python -m qapu_cli.main device list
```

(or, once installed via `pip install -e .`: just `qapu devices list` with the same env vars set.)
