Skip to main content

lr-lumentest-cli

CLI for the LumenRadio LumenTest production test station server.

Installation

pip install lr-lumentest-cli

Quick Start

# 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, login state, and which station you're pointed at
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]
  # Names the station that answers ([station].id from its config.toml), so you
  # can confirm which bench you are pointed at, alongside login state and
  # server health. Reports config_stale when config.toml on disk differs from
  # the running configuration — a hand edit takes effect only on a restart —
  # and config_readable when the station cannot read the file at all, which
  # also implies config_stale. --output json also carries boot_id, the
  # station's per-process identity, which changes across a restart.
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 [--bundle] [--invalidate] [--output rich|json]
  # PATH: directory or .zip/.tar.gz archive.
  # For a directory carrying a .arena.json sidecar (a raw `gladiator get-files
  #   --recursive` checkout), product import regenerates artifact-manifest.json
  #   from the Arena metadata before upload. The digests then match the files
  #   on disk. A directory with a manifest but no .arena.json uploads as-is.
  #   The sidecars are the source: an edit to a generated manifest does not
  #   survive the next import, so a value that must persist belongs on the
  #   Arena attachment. The command reports each files[] entry, title, role
  #   and top-level key the regeneration changed or dropped, and each title
  #   the package's own step code resolves that no entry declares.
  #   sns_article has no Arena source and is carried over.
  #   A sidecar that does not parse or declares no article aborts with exit 1
  #   and leaves the manifest already on disk byte-for-byte intact.
  # --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".
  # --invalidate: discard whatever the station has staged for this
  #           article/revision before preflighting, so every declared file
  #           uploads again instead of reusing what's already there. The
  #           escape hatch when an import keeps failing at commit on a file
  #           the manifest no longer declares — the staged tree only ever
  #           grows, so a package fixed in Arena after a first failed
  #           upload can otherwise leave an orphan file behind forever.
  #           Requires a directory; an archive import never touches the
  #           station's staging tree, so there is nothing to invalidate.
  # 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      [SEQUENCE] [--article ARTICLE]
lumentest sequence selection get   [SEQUENCE] [--article ARTICLE]
lumentest sequence selection set   [SEQUENCE] [--article ARTICLE] --steps 1,2,5
lumentest sequence selection clear [SEQUENCE] [--article ARTICLE]
  # SEQUENCE is optional when the article declares exactly one; --article
  # defaults to the active product.

lumentest test start   [--serial SN] [--article ARTICLE] [--sequence ID]
  # 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.
  # Without --serial it prompts for a scan, except on a sequence that reads
  # the serial off the unit part-way through the run (`serial_read_in_sequence`),
  # which the station reports on GET /api/sequences and which refuses a
  # supplied serial with 422 — there the run starts straight away.
  # 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.  SEQUENCE — like --sequence above — is the ID column of
  # `sequence list`, not its Name: the two differ once a sequence declares
  # a name of its own.
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).
  # Prints a warning naming the config.toml path and the diverging keys when
  # the file on disk differs from the running configuration, pointing at
  # `lumentest config reload` to apply it — or, if the station cannot read
  # the file at all, a warning naming the path and the reason instead.
lumentest config get   KEY  [--output rich|json]
  # Warns when this key (or a key beneath it) differs from config.toml on
  # disk, or the station cannot read the file, and points at
  # `lumentest config list` for the file path and the full list of keys.
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. Warns, pointing at `lumentest config list`, when
  # the station's config.toml still diverges after the write — an unrelated
  # hand edit elsewhere in the file that this write did not reconcile.
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 config reload [--output rich|json]
  # Re-reads config.toml into the running station, which is what applies a
  # hand edit, an scp, or a configuration-management push. The file is
  # validated exactly as it is at startup and applied only if the whole of
  # it is sound: one that will not parse or will not validate is rejected
  # with the reason, and the station keeps the configuration it already had.
  # Names the dotted keys the re-read changed, and warns for any of the three
  # restart-required keys among them — saved, but not applied until the
  # station restarts. Also re-syncs the station's process environment from
  # [env], so a variable added or removed in the file agrees with what the
  # next run executes against.

lumentest station restart [--force] [--output rich|json]
  # Restarts the station over the network — the way to apply station.host,
  # station.port and debug.results_dir. The station finishes in-flight
  # requests, shuts down and exits; the service supervisor installed with it
  # starts it again. Refused when [station].supervised is false (nothing
  # would bring the station back — a station run by hand rather than from
  # the .deb or .msi) and while a test is running (it abandons a part-tested
  # DUT and loses the run); --force overrides both. Waits for the station to
  # answer again and reports its version. See doc/installation.md §3.11.

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:

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

Release files for lr-lumentest-cli 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lr-lumentest-cli 0.8.0
File Size Uploaded
lr_lumentest_cli-0.8.0.tar.gz 160.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lr-lumentest-cli 0.8.0
File Interpreter ABI Platform
lr_lumentest_cli-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 245.4 kB

Release files / lr_lumentest_cli-0.8.0.tar.gz

Download URL lr_lumentest_cli-0.8.0.tar.gz
Size 160.1 kB
Tags Source
SHA-256 checksum
How to use checksums
979085e95ab1d792b75de1cbc542a4058044fbf60c476f42ac7a26496b697d75
BLAKE2b-256 checksum
How to use checksums
6c5329a5e4abf948d88b465d04a0c54c5a2abff444db08d89d46af724218dc9a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / lr_lumentest_cli-0.8.0-py3-none-any.whl

Download URL lr_lumentest_cli-0.8.0-py3-none-any.whl
Size 85.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4fab0eca3f742e68ec4b409f57b1f86ca62f3670692ada81f2e8dcb121dda317
BLAKE2b-256 checksum
How to use checksums
2648d38bd196cc4486a631d4a262d7bfd6c0237e92012ce706e3fdc3e399e3ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.2

2 release files

0.5.1

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