Skip to main content

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:

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:

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

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.

qapu trend <device_id> <variable_id>            # GET /hermes/trend/{device_id}/{variable_id} - last 20 raw readings + trend
qapu trend <device_id> <variable_id> --last 50  # last N raw readings instead of the default 20
qapu trend <device_id> <variable_id> --days 2   # daily min/avg/max over the last 2 days, instead of raw readings
qapu trend <device_id> <variable_id> --days 30  # ...or the last month
qapu trend <device_id> <variable_id> --json

qapu trend shows a per-variable history plus a simple linear-regression trend line (slope + direction). Two modes, picked by which option you pass:

  • Raw readings (--last N, default N=20): the measurement cache's rolling buffer - bounded to its own depth (~50 most recent packets), not a time window. Good for "what's it doing right now."
  • Daily aggregates (--days N): one row per calendar day (min/avg/max/count), from the same cache the /measurement/{device_id}/history endpoint reads - this is what actually covers multi-day/month windows, since the raw buffer doesn't go back that far. --days wins if both are given.

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

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qapu_cli-0.4.0.tar.gz (16.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qapu_cli-0.4.0-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file qapu_cli-0.4.0.tar.gz.

File metadata

  • Download URL: qapu_cli-0.4.0.tar.gz
  • Upload date:
  • Size: 16.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for qapu_cli-0.4.0.tar.gz
Algorithm Hash digest
SHA256 d3acd2b0e107307a0f6caa78ef3d136768d46c20657e1cad3cd463f63b8e01bd
MD5 94a007c226fb5bd19bdd2e6bd47b6a99
BLAKE2b-256 6024cc874a73d0f5b391a2db7d02f4d43dc5375e8eb394602b6424bfab0cc943

See more details on using hashes here.

File details

Details for the file qapu_cli-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: qapu_cli-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 14.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for qapu_cli-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 830bd97748c2c139365f868e4c82e1df03763602073604804e5a1f6e2a625418
MD5 5cb7ce018f72ab47d07457e48a63d35f
BLAKE2b-256 43b66ada370a3bce9a19426606159549c702fdbd645b40048caaf576ef2ae27a

See more details on using hashes here.

Release history Release notifications | RSS feed

0.6.5

2 files

0.6.4

2 files

0.6.3

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.1

2 files

0.5.0

2 files

This release

0.4.0 This release

2 files

0.3.1

2 files

0.3.0

2 files

0.1.0

2 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