Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
"name": "reolink-cli",
"description": "Operate Reolink cameras locally via reolink-cli: discover, login, inspect, configure, preview, snapshot, and control PTZ/light/audio/detection/recording.",
"version": "0.15.0",
"version": "0.16.0",
"source": "./",
"author": {
"name": "Reolink"
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "reolink-cli",
"description": "Operate Reolink cameras locally: discover, login, inspect, configure, preview, snapshot, and control PTZ/light/audio/detection/recording.",
"version": "0.15.0",
"version": "0.16.0",
"author": {
"name": "reolink"
},
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "reolink-cli",
"version": "0.15.0",
"version": "0.16.0",
"description": "Plugin for operating Reolink cameras through the local CLI.",
"author": {
"name": "Reolink"
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "reolink-cli",
"displayName": "Reolink CLI",
"description": "Operate Reolink cameras locally: discover, login, inspect, configure, preview, snapshot, and control PTZ/light/audio/detection/recording.",
"version": "0.15.0",
"version": "0.16.0",
"author": {
"name": "Reolink"
},
Expand Down
83 changes: 83 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,89 @@ All notable changes to the public `reolink-cli` distribution are documented here
This is the customer-facing release history; it tracks the LAN-only (external)
builds published as GitHub Releases.

## [0.16.0] — 2026-09-02

Snapshots got faster, and one class of snapshot stopped failing outright. Both
turned out to be our bugs, not the camera's — found by timing each phase of a
capture instead of assuming the wait was physics.

### Fixed

- **`--stream sub` was silently returning a full-resolution frame on some
cameras.** `<fullFrame>` is a model-specific snapshot mode (the reference SDK
documents it as "snap full frame pic, *for sd7*", to be used *together with*
the sub stream). The CLI sent `fullFrame=1` unconditionally, to every device.
On a camera that implements the flag, that overrides `<streamType>` — you ask
for the small preview-sized JPEG and the device dutifully encodes and sends
the full-resolution one.

Measured on a dual-lens child behind a Home Hub 2, same camera and otherwise
identical request:

```
resolution size awake asleep (first capture)
--stream sub
fullFrame=1 3840x2160 875 KB 9.34 s 23 s
fullFrame=0 896x512 58 KB 1.92 s 6.46 s

--stream main
fullFrame=1 3840x2160 861 KB 17.1 s
fullFrame=0 3840x2160 860 KB 5.2 s
```

So the default stream is **~5x faster and ~15x smaller**, and its cold-start
case drops from 23 s to 6.5 s.

`--stream main` is worth reading twice: it returns the same resolution and
very nearly the same bytes either way, so nothing about the image changed —
but the flag was routing the capture down a much slower and far less stable
path. Three runs took 11.8 / 17.1 / 28.8 s with it, against 4.7 / 5.2 / 7.4 s
without. Full-resolution captures on these cameras are roughly **3x faster**
as a result.

Cameras that never implemented the flag measured the same either way, which is
exactly why this hid for so long: only the models that *support* the mode
looked broken, and they looked like they were ignoring `--stream` rather than
following an instruction we should not have been sending.

- **Snapshots from a sleeping or high-resolution camera could fail instead of
just being slow.** The transport's read budget was a flat 10 s, and it bounds
a *single* read — while a snapshot's `cmd 109` is one long silence: a battery
camera behind a hub has to bring its whole video pipeline (sensor, ISP,
encoder) up before it sends the first byte.

Measured on a Home Hub 2: a sub-stream capture from a sleeping child spent
**5.1 s** in that silence, and the 4K dual-lens child needed **23 s** when
three channels were woken concurrently. Anything past 10 s came back as
`tcp read timeout after 10s` — a failed capture, not a slow one.

The budget is now 30 s, which covers the wake. It stays a single global value
rather than a per-command one: VOD download and preview first-frame have the
same "one long wait" shape, and a per-call timeout is something every future
media path would have to remember to pass. The trade is that a device which
accepts the connection and then goes mute takes 30 s to report — rare, and
well inside the CLI's own 60 s response budget. Timeout messages now quote the
real value instead of a hard-coded "10s".

### Added

- **`events history` — search the event log the device itself recorded**
(hub / NVR; v2.0 cmds 516/517/518). This is a different store from
`events query`: `query` and `stream` read the gateway's in-memory ring of live
pushes (the most recent few hundred, lost when the gateway restarts), while
`history` asks the device for the events it wrote down, so it reaches back as
far as the device keeps them.

```
reolink-cli --channel 1 events history --since 7d --types people,motion
```

Takes `--from`/`--to` or a relative `--since`, an optional `--types` filter,
and `--limit`. A hub files events per paired camera, and the CLI resolves that
sub-device automatically from `--channel`. Standalone cameras have no event
log and answer `400`; an empty list from a hub means the window genuinely held
no matching events.

## [0.15.0] — 2026-09-01

Four community issues, and along the way the answer to a question 0.14.2 left
Expand Down
8 changes: 8 additions & 0 deletions checksums/v0.16.0.sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
4f67679fb1e1c41e80ab71100494676b17b377308b98fb3ed89aa83b5cea0354 reolink-cli-0.16.0-external-darwin-arm64.tar.gz
d2ee04a121be8b07d054d133cd60a91f5df5e4839d29466fb2a9e8060d0b6d23 reolink-cli-0.16.0-external-linux-arm64-musl.tar.gz
2bcc53b3c407d9049a7f89ef12eff6303549539455b4d03e481b4a9e82720cf5 reolink-cli-0.16.0-external-linux-arm64.tar.gz
3f3aa7d724f5f3d1f3445e24a769cefee956d7f562ca29cd491ca15f381f5b41 reolink-cli-0.16.0-external-linux-armv6.tar.gz
8c8398970314808ffa0dd641f4f1aff74a6db9860c53b60bc4c8c0d9df24622b reolink-cli-0.16.0-external-linux-armv7.tar.gz
a607488940cc93387283fb24fe12f7bd02a4b324a3b22c454235738191216773 reolink-cli-0.16.0-external-linux-x86_64-musl.tar.gz
776769bb8b86901329000e059e345d43da58a53a831f7b5753c1207250c2665a reolink-cli-0.16.0-external-linux-x86_64.tar.gz
0a5893ab0af3aba45732f089e733f8e405e8fccf3f5bd3dad91a948bbb226b7a reolink-cli-0.16.0-external-windows-x86_64.zip
2 changes: 1 addition & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "reolink-cli",
"description": "Operate Reolink cameras locally via reolink-cli: discover, login, inspect, configure, preview, snapshot, and control PTZ/light/audio/detection/recording.",
"version": "0.15.0",
"version": "0.16.0",
"contextFileName": "GEMINI.md",
"mcpServers": {
"reolink": {
Expand Down
2 changes: 1 addition & 1 deletion openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "reolink-cli",
"name": "Reolink CLI",
"description": "Operate Reolink cameras locally via reolink-cli: discover, login, inspect, configure, preview, snapshot, and control PTZ/light/audio/detection/recording.",
"version": "0.15.0",
"version": "0.16.0",
"author": {
"name": "Reolink"
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "reolink-cli",
"version": "0.15.0",
"version": "0.16.0",
"type": "module",
"description": "Operate Reolink cameras locally via reolink-cli.",
"main": ".opencode/plugins/reolink-cli.js",
Expand Down
7 changes: 4 additions & 3 deletions skills/reolink-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Resolve ambiguity before picking a command. If still unclear, **ask with options
| **cpu / memory / device load / is the camera overloaded / stuttering** | **`config get performance`** | Live reading from the device: `cpuUsedPercent`, `codeRate`, `netDataRate`. Read-only and instantaneous — two calls a second apart legitimately differ. Not every model implements it; those answer a device-level rejection. Distinct from `benchmark`, which times the **client** round trip, not the camera. |
| **rtsp / rtmp / flv address / stream URL / HA / Frigate / go2rtc / VLC** | **`stream url [--kind rtsp,rtmp,flv] [--stream main,sub,ext] [--with-auth]`** | Default `--kind rtsp --stream main`. `--with-auth` only when user explicitly wants one-shot pasteable URL. NVR: `device expand` then `--tag <nvr>`. Detail in `references/media.md`. |
| nvr with 8 channels / RLN sub-cameras / channel N | `device expand <nvr-name> [--yes \| --names A,B,C]` | **NVR only**. Registers one entry per channel, tagged with parent name. After: `--tag <nvr-name>` fans out. |
| command rejected 400 / "model doesn't support it" / battery camera on a hub / first command after a pause | Nothing — the gateway already retries. `attempts` in the answer says which try got through (3 is normal after a pause, 1 during a burst) | A battery child behind a hub **falls asleep after ~2 min of quiet** and then rejects commands with a bare 400, indistinguishable from unsupported. Measured: the wake costs a flat 3 attempts (~1.6 s), and the refused request is itself the wake. If it still fails after the budget, `info` → `channel.loginState` = `standby` explains it — but **that field lags**, so it explains a failure, it cannot pre-flight one. |
| command rejected 400 / "model doesn't support it" / battery camera on a hub / first command after a pause | Nothing — the gateway already retries. `attempts` in the answer says which try got through (3 is normal after a pause, 1 during a burst) | A battery child behind a hub sleeps and then rejects commands with a bare 400, indistinguishable from unsupported. **When** it sleeps is not predictable from idle time alone (one day's readings said ~2 min; later overnight windows found it awake), so do not try to pre-empt it. What is consistent: a sleeping camera costs a flat 3 attempts (~1.6 s) to wake, and the refused request is itself the wake. If it still fails after the budget, `info` → `channel.loginState` = `standby` explains it — but **that field lags**, so it explains a failure, it cannot pre-flight one. |
| dual-lens / two lenses / bullet+PTZ in one camera / wide and telephoto / second lens | `info` first (`channel.views`, `channel.dualLens`), then `--view N` on config commands | `--view` is a **third** axis: `--channel` picks the camera, `--view` picks the lens, `--stream` picks that lens's encoding profile. Verified per-view: `encode`, `privacy mask`, `detect motion`, PTZ. **Not** per-view: `snapshot` (device answers 400 for view≥1), `preview`/`stream url` (protocol has no selector — these now **reject** `--view N` rather than silently handing back view 0), image/ISP (shared). Default 0 = the only view an ordinary camera has. |
| **preview / take a look / watch N minutes / watch live** | **`preview play`** (opens ffplay window) | **DEFAULT to `play`, not `capture`.** User wants a live window, not a file. |
| record N seconds / save a clip / save it | `preview capture --packets ≈N×25` | Only when user explicitly says save / record / export. Packet count, not seconds; ~25 fps main, ~10 fps sub |
Expand All @@ -103,6 +103,7 @@ Resolve ambiguity before picking a command. If still unclear, **ask with options
| manual record / record now / start recording | — | **Not supported** on Reolink IPCs; fall back to `record schedule set --enable/--disable` |
| download yesterday / download recording | `vod search --since 24h` → `vod download NAME` | |
| any alarms / any alarms today | `events query --since 24h` | Requires gateway |
| event history further back than the live buffer / what happened last week / hub event log | `--channel N events history --since 7d [--types people,motion]` (hub/NVR only) | Reads the device's recorded event log (cmd 516/517), not the ~500-entry live ring. Standalone IPCs 400. Empty list = no matching events; a hub can go briefly quiet after heavy use, retry after a pause. |
| voice alert / voice announcement / announce when someone arrives / play voice when a person is detected | `audio talk` (see `references/voice-alert.md`) | PCM16 LE mono only; needs `capabilities.audioTalk=1` |
| detect X do Y / automation / event trigger / detect-then-do | **Must** `events monitor init/check/run` with TOML rule. **Forbidden** ad-hoc bash `while + events query` loops — see `references/event-monitor.md`. |
| is the monitor running / has a rule fired / monitor status / what fired recently | `events monitor status` (pid + cursor + lastFires + per-rule `expired`/`secondsRemaining`) or `events monitor history --last N [--rule NAME] [--since 30m]` | Inspect the running daemon without restarting. Filters apply **before** `--last`, so `--rule watch --last 20` is that rule's last 20 fires, not whatever survives the global tail. |
Expand Down Expand Up @@ -214,7 +215,7 @@ Signatures only — run `<cmd> --help` for flag details; see `references/<topic>
`led`, `device-name`, `language`, `time-zone`, `time-format`, `network`, `osd`, `osd-format`, `system-general` (get-only).
*(The paths `image` / `audio` / `alarm` / `alarm-policy` / `ai` were removed — they were stubs. Use dedicated commands below.)*

**Preview / Snapshot:** `preview capture|play|start|stop`, `snapshot [-o FILE] [--stream]`
**Preview / Snapshot:** `preview capture|play|start|stop`, `snapshot [-o FILE] [--stream]`. `--stream sub` (the default) is the fast path — a small ~900x512 JPEG, ~2s on a woken camera; `--stream main` is a full-resolution frame at ~5s on a 4K model. **A battery camera behind a hub sleeps (when is not predictable from idle time — see the 400 row above), and the first capture after it does waits for its whole video pipeline to boot: measured 5s (sub) to 23s (4K, three channels waking at once) before the device sends a single byte.** That wait is device-side and unavoidable — it is not a hang, and the timeout budget covers it. A capture during a burst is sub-second; treat the first one after any gap as a warm-up rather than trying to predict which one pays the wake.

**Benchmark:** `benchmark [--iterations N] [--phases connect,login,info,snapshot] [--reuse-session] [--warmup M]` — per-phase p50/p95/p99/mean/stddev. Read-only.

Expand Down Expand Up @@ -263,7 +264,7 @@ Types: `manual|sched|io|md|people|vehicle|face|dog_cat|visitor|other|package`

**Notifications:** `notify push {get|set [--interval SECS] [--rich N] [--consent N]}`

**Events (gateway required):** `gateway start [--addr HOST:PORT]` once, then `events {query [--last N] [--after] [--since] [--types T,...] | stream [--timeout] [--types] | monitor {init|check|run|reload|status|history}}` with `--gateway-addr HOST:PORT`. `monitor` is the declarative rule engine — see `references/event-monitor.md`. **Event `--types` vocabulary is different from VOD `--type` vocabulary** — events use `motion|people|vehicle|face|dog_cat|visitor|package|cry` (no `md`, `sched`, `manual`, `io`, `other`). `md` is VOD-only. `--since` on `events query` accepts `<N>m|h|d` only (NOT seconds) — use `1m` minimum.
**Events (gateway required):** `gateway start [--addr HOST:PORT]` once, then `events {query [--last N] [--after] [--since] [--types T,...] | history [--from ISO --to ISO | --since] [--types] [--limit N] | stream [--timeout] [--types] | monitor {init|check|run|reload|status|history}}` with `--gateway-addr HOST:PORT`. `monitor` is the declarative rule engine — see `references/event-monitor.md`. **Event `--types` vocabulary is different from VOD `--type` vocabulary** — events use `motion|people|vehicle|face|dog_cat|visitor|package|cry` (no `md`, `sched`, `manual`, `io`, `other`). `md` is VOD-only. `--since` on `events query` accepts `<N>m|h|d` only (NOT seconds) — use `1m` minimum. **`events history` is a separate store from `events query`**: `query`/`stream` read the gateway's in-memory ring of live pushes (recent ~500, lost on restart), `history` asks the device (hub/NVR) for its recorded event log by time window (cmd 516/517, per-sub-device UID resolved from `--channel`); standalone IPCs 400.

**WiFi:** `wifi get` — diagnostic: read current SSID / authMode / encryptType / channel / countryCode. The `key` field is always redacted to `""` by the device firmware. Use to confirm a previous `wifi set` actually applied (sometimes Phase 4 rediscover times out cross-subnet but the SET succeeded — `wifi get` confirms). `wifi set --ssid <SSID> {--psk <PSK> | --psk-stdin} [--no-test] [--no-update-registry] [--rediscover-timeout-secs <N> (default 90)] [--hidden]` — push new SSID+PSK; 4-phase orchestrator: (1) capability probe, (2) WifiTest pre-check, (3) commit, (4) BCDI rediscover + registry auto-update. Exit codes follow the CLI-wide error classes: 0 success / 1 input / 2 network (device unreachable, P2P/TCP timeout — retryable) / 3 auth (device rejected credentials or commit) / 4 device error / 5 protocol (includes "wifi did NOT switch" read-back verify failure). Branch on the JSON `error.code` string when possible; the numeric exit is the class, not a wifi-set-specific code. Safety: device on wireless with `wifiTestAtWireless=0` cap will refuse without `--no-test`; the device itself also enforces this server-side. **Recommended path**: wired device → `wifi set --ssid X --psk-stdin` then echo PSK in. **Credential safety**: **Must** use `--psk-stdin` (pipe PSK via stdin) rather than `--psk PLAIN` on argv — same rule as passwords.

Expand Down
Loading