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

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.3.0.tar.gz (13.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.3.0-py3-none-any.whl (11.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: qapu_cli-0.3.0.tar.gz
  • Upload date:
  • Size: 13.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.3.0.tar.gz
Algorithm Hash digest
SHA256 995c3f7efb461425b9b74e606e1ba3b0ddf95b3d95af5e4dd7550b5e6fdaa07f
MD5 d63e3dc01045e16e6f32e170d30b913d
BLAKE2b-256 b3f847e2a126633d382f7e0cb2fd3b129878327af9cbad73b4cd08e7e8fa2816

See more details on using hashes here.

File details

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

File metadata

  • Download URL: qapu_cli-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 11.6 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a448a0904a887c315e0e9bebdedea88351117d3f6acc13bcad818b2be801569d
MD5 7e29584102e0abc76098c3fa2655d852
BLAKE2b-256 3ded535ff700d17f81048572f60752bb9f89ba123b63aa0474d75063ee2da05f

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

0.4.0

2 files

0.3.1

2 files

This release

0.3.0 This release

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