Metadata-Version: 2.4
Name: lr-lumentest-cli
Version: 0.5.1
Summary: CLI client for the LumenRadio LumenTest production test station: import artifact packages, run test sequences, and read results over HTTP.
Author-email: Jonas Estberger <jonas.estberger@lumenradio.com>
License-Expression: MIT
Project-URL: Homepage, https://gitlab.com/lumenradio/production/lumentest
Project-URL: Changelog, https://gitlab.com/lumenradio/production/lumentest/-/blob/main/CHANGELOG.md
Keywords: LumenRadio,LumenTest,production test,test station,CLI
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Environment :: Console
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13.7
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.8
Requires-Dist: mnemonic>=0.21
Provides-Extra: dev
Requires-Dist: build>=1.2.1; extra == "dev"
Requires-Dist: twine>=6.1; extra == "dev"
Requires-Dist: pytest>=8.4; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: black>=25.9; extra == "dev"
Provides-Extra: package
Requires-Dist: pyinstaller==6.21.0; extra == "package"
Dynamic: license-file

# lr-lumentest-cli

CLI for the LumenRadio LumenTest production test station server.

## Installation

```bash
pip install lr-lumentest-cli
```

## Quick Start

```bash
# Point the CLI at your station server and identify yourself by scanning
# your badge (the handheld scanner types the badge id at the prompt)
lumentest login --base-url http://10.0.0.1:17080 --badge-id LOP0000001

# Check server health and login state
lumentest status

# Scan and list imported products
lumentest product scan
lumentest product list

# Run a full test sequence interactively (prompts on ALERT steps)
lumentest test start --serial SN-00001

# Run a single step (for development / debugging)
lumentest test step production-test flash-firmware --serial SN-00001
```

## Command Reference

```
lumentest login    [--base-url URL] --badge-id ID
  # ID is the scanned QR badge; the server resolves it to your operator id.
lumentest logout
lumentest status   [--output rich|json]
lumentest --version

lumentest product list   [--output rich|json]
  # Table includes a Build Words column — see doc/build-words.md. The manifest
  # digest is not shown: read it from --output json when you need the digest.
lumentest product active
lumentest product set    ARTICLE [REVISION]
lumentest product remove ARTICLE [REVISION] [--yes]
lumentest product scan   [--rescan] [--output rich|json]
  # Scans the station's artifact_cache/, registers every valid package, and
  # reports each package's declared env variables (from parameters.toml) —
  # the value a run will see, where it came from (config.toml's [env], the
  # station's own environment, or the package default) and which are still
  # unconfigured — alongside its prerequisites (the external tools its
  # sequences need) against the station itself. Each unconfigured variable's
  # line carries the exact `lumentest config set env.NAME <VALUE>` command to
  # fix it; a set value applies to the running station immediately, so
  # re-running scan is enough to confirm it, for a variable and for a tool
  # newly installed on PATH alike — no restart needed either way. --output
  # json emits the raw payload.
lumentest product import PATH [--update] [--bundle] [--output rich|json]
  # PATH: directory or .zip/.tar.gz archive.
  # --update: recompute SHA-256 checksums in artifact-manifest.json before
  #           uploading — useful during step development to skip manual hashing.
  # --bundle: pack PATH into <PATH>.tar.gz (manifest + declared files only)
  #           next to it instead of uploading — for email/USB delivery;
  #           import the archive later with the same command. Requires a
  #           directory; passing an archive file just warns and exits.
  #           Prints the package's Build Words (doc/build-words.md) so a
  #           technician can read them off a locally built bundle without a
  #           running server; --output json carries them as "build_words".
  # Reports the package's declared env variables the same way `product scan`
  # does: the value a run will see, where it came from, and which still need
  # a value. Fix an unconfigured one with the `lumentest config set` command
  # printed on its line, then re-run `product scan` to confirm — same as
  # confirming a newly installed prerequisite tool, neither needs a restart.
  # Only artifact-manifest.json's declared files (and declared extra
  # sequences) are packed and uploaded — the manifest is the contract for
  # what the package needs. A package that needs a file its manifest does
  # not declare must have that file added to the manifest, not bundled some
  # other way.
  # A directory import first checks with the station which files it already
  # has (by digest, including across every other cached revision) — a
  # retry after an interrupted upload resumes instead of re-sending
  # everything. Only what's actually missing is uploaded, with a progress
  # bar over just those bytes; the command then reports how many files were
  # reused, e.g. "12 of 24 files already on the station (302 MB reused)".
  # An existing .zip/.tar.gz archive still uploads as a single one-shot
  # transfer. --output json moves all of the above to stderr so stdout
  # carries nothing but the final JSON — see "Machine-readable output".

lumentest sequence list       [--article ARTICLE] [--output rich|json]
lumentest sequence steps      ARTICLE SEQUENCE
lumentest sequence selection get   ARTICLE SEQUENCE
lumentest sequence selection set   ARTICLE SEQUENCE --steps 1,2,5
lumentest sequence selection clear ARTICLE SEQUENCE

lumentest test start   [--serial SN] [--article ARTICLE] [--sequence NAME]
  # Blocks with an interactive Rich UI; handles ALERT prompts; exits with
  # the run's terminal status: 0 PASS, 1 FAIL, 2 ABORTED, 3 ERROR (outcome
  # undetermined — station unreachable or run gone). The operator comes
  # from the bearer token established by `lumentest login`, not a flag.
  # The run header shows the package's Build Words when the station reports
  # them.
lumentest test step    SEQUENCE STEP [--article ARTICLE] [--serial SN]
  # Runs one step via the same interactive UI.  STEP is a name or 0-based index.
lumentest test status  [--output rich|json]
  # One-shot snapshot of the currently active test run.

lumentest result list  [--limit N] [--serial SN] [--output rich|json]
  # --serial SN filters to runs for one device (exact dut_id match); the station
  #   filters server-side so high-throughput stations don't ship the full history.
lumentest result show  [RUN_ID]
  # RUN_ID is optional: omit it to show the most recent run.
lumentest result logs  [RUN_ID] [--step IDX] [--stream stdout|stderr|traceback]
                      [--download DIR] [--output rich|json]
  # RUN_ID is optional: omit it to fetch the most recent run's logs.
  # Without --step: fetches the complete log bundle via GET /logs/all — run error,
  #   structured journal, and every step's stdout/stderr/traceback.  --output json
  #   emits the raw JSON bundle; --download DIR writes the tree to DIR/<run_id>/
  #   (error.txt, log.jsonl, steps/<name>/{stdout,stderr,traceback}.txt, omitting
  #   empty files).  --output json takes precedence when both flags are given.
  # With --step IDX: shows that step's stdout, stderr, and traceback (or a single
  #   stream via --stream).
lumentest result export [RUN_ID] [--all] [--serial SN] [--limit N]
                        [--out DIR] [--output rich|json]
  # Downloads one .tar.gz station snapshot via GET /api/results/export: the
  #   selected run(s) verbatim under results/ (run.json, qrm-payload.json, the
  #   journal, every step's captured streams) plus the station's own log,
  #   state.json, operator_stats.json and effective config under station/ —
  #   laid out exactly as the station stores them on disk. RUN_ID / --all /
  #   --serial / --limit govern results/ only; the station context is always
  #   included. Bare `result export` packs only the most recent run; --all
  #   opts into every retained run. RUN_ID is mutually exclusive with --all,
  #   --serial and --limit.
  # --out DIR: directory to write the archive into (default: the current
  #   directory). The filename is stamped by the station itself from its own
  #   [station].id and a UTC timestamp, so an export stays identifiable once
  #   it is off the station.
  # artifact_cache/ package binaries are never packed — represented instead by
  #   an inventory (article, revision, size, file count); Arena is the system
  #   of record for firmware. Re-send a run to QRM by hand with
  #   `qrm result upload <run>/qrm-payload.json` against the extracted tree.
  # An export contains the station's QRM service password, session token
  #   hashes, operator identities, and whatever a step or tool printed. Treat
  #   an archive as a secret — see doc/troubleshooting.md § Collecting a
  #   station snapshot for support.

lumentest config list  [--output rich|json]
  # Flattens config.toml to dotted keys (station.id, env.NRF_TOOLS,
  # timeouts.default_step_s, ...) so a technician can scan the whole
  # effective configuration in one table. Secrets (qrm.password,
  # serial.client_secret, user password hashes) show as "<redacted>" and are
  # marked in the Notes column, alongside keys that need a station restart
  # to take effect (station.host, station.port, debug.results_dir).
lumentest config get   KEY  [--output rich|json]
lumentest config set   KEY VALUE [--json] [--output rich|json]
  # VALUE is sent as a raw string and coerced through the server's pydantic
  # validation (e.g. "90" -> the int 90); pass --json to send VALUE parsed
  # as JSON instead, for list/dict/explicitly-typed values. This is how a
  # newly required package env variable gets set without restarting the
  # station: `lumentest config set env.SOME_VAR value` then
  # `lumentest product scan`. A key flagged restart-required still saves,
  # but a visible warning says it only takes effect after the next restart.
  # Setting a secret back to its own masked "<redacted>" value is rejected,
  # so a `config get` / `config set` round trip can never silently
  # overwrite a credential.
lumentest config unset KEY
  # Resets KEY to its model default; for the free-form [env] and
  # [test].fake_tools maps this removes the entry instead. A required key
  # with no default (e.g. station.id) is rejected. A restart-required key
  # carries the same warning `config set` gives — the reset is saved, but
  # the running station keeps the old value until it restarts.

lumentest operator whoami [--output rich|json]
  # Resolve the operator behind the stored token via GET /api/auth/whoami.
  # Exits 1 if the token is missing, unknown, or expired.

lumentest operator stats [OPERATOR_ID] [--stations URL1,URL2,...] [--output rich|json]
  # Per-operator statistics aggregated across stations (counters add,
  # last_login = max).  Omit OPERATOR_ID to list every operator seen.
  # --stations defaults to the logged-in station.
```

## Machine-readable output

Every command above that takes `--output json` writes **exactly one JSON document
to stdout and nothing else**, so a caller can pipe it straight into a parser:

```bash
lumentest product import --bundle --output json ./400-1011_A | jq -r .build_words
```

Everything meant for a human — progress bars, warnings, "generated the
manifest" — goes to **stderr** in JSON mode, and errors always do. Redirect it
away with `2>/dev/null`, or keep it to watch a long import while still capturing
the result.

A command that finds nothing to report emits `null` rather than staying silent,
so stdout parses unconditionally.

`result logs --step IDX` is the one exception: it has no JSON form, so `--output`
is inert there and the step's captured streams go to stdout either way. Piping
that output works with the flag or without it.

In `--output rich` (the default) the human output goes to stdout as usual.

## Server URL

The CLI reads the server URL from (in order of priority):

1. `--base-url` flag on `lumentest login`
2. `LUMENTEST_URL` environment variable
3. Stored value in `~/.config/lumentest/login.json`
4. Default: `http://127.0.0.1:17080`
