Communitygithub.com

reolink/reolink-cli

Official command-line tool for Reolink IP cameras, doorbells and NVRs — LAN only, no cloud. Snapshots, PTZ, RTSP/RTMP stream URLs, two-way audio (talkback/TTS), motion and AI detection, event monitoring, VOD download. JSON output and a built-in MCP server. Works with Home Assistant, Frigate and go2rtc.

Qu'est-ce que reolink-cli ?

reolink-cli is a Claude Code agent skill that official command-line tool for Reolink IP cameras, doorbells and NVRs — LAN only, no cloud. Snapshots, PTZ, RTSP/RTMP stream URLs, two-way audio (talkback/TTS), motion and AI detection, event monitoring, VOD download. JSON output and a built-in MCP server. Works with Home Assistant, Frigate and go2rtc.

Compatible avecClaude CodeCodex CLICursorGemini CLI
npx skills add reolink/reolink-cli

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Reolink Camera Operator

Overview

Primary surface: reolink-cli (JSON stdout by default). Don't start the MCP server unless asked. When the user needs non-CLI access (browser, curl, other languages), point them at the gateway's POST /api.

Slash commands (Claude Code only) — prefer these for fixed/single-action intents; skip the intent-mapping overhead:

SlashEquivalentWhen to use
/reolink-cli:statusreolink-cli statususer asks about current state / dashboard / "how are things"
/reolink-cli:featuresreolink-cli featuresuser asks what the plugin can do / "what can it do"
/reolink-cli:scanreolink-cli discoverscan local network / "scan"
/reolink-cli:devicesreolink-cli device listlist registered cameras
/reolink-cli:cache-cleanreolink-cli cache clean dry-run→applyclear old snapshots / "clear cache"
/reolink-cli:updatereolink-cli self-updateupgrade to the latest release (checks GitHub, no-op if current)
/reolink-cli:uninstallreolink-cli setup --uninstall --purge --no-interactivecomplete uninstall

Claude Code runtime: Must use the slash command when the user's intent matches one of these seven exactly — faster (no LLM latency), deterministic, and discoverable via / autocompletion.

Other agents (Codex / Cursor / Copilot / Gemini): these surfaces don't support plugin-defined slash commands, so the "slash-first" rule doesn't apply — skip straight to running the equivalent reolink-cli … CLI command directly. Skill-driven intent mapping handles everything else.

All agents: Fall back to skill-driven CLI chaining for compound / fuzzy intents ("announce when a person is detected", "make it brighter") that aren't covered by the slash list / seven fixed actions above.

The gateway is mandatory for almost every control command. The CLI routes through 127.0.0.1:9000 by default (set in config.toml under the platform config dir — ~/.config/reolink-cli/config.toml on Linux, ~/Library/Application Support/reolink-cli/config.toml on macOS, %APPDATA%\reolink-cli\config.toml on Windows — as gateway-addr). If you see gateway connect failed: Connection refused, the gateway isn't running — start it once:

reolink-cli gateway start --addr 127.0.0.1:9000 &

Works without the gateway: device add|list|update|remove|resolve|show, config init, discover, features, doctor, cache status|clean — everything that reads local config or talks to the network directly. (Verified by running each with no gateway listening.) Everything else (ping, login, info, config get/set, image, osd, ptz, detect, light, audio, preview, snapshot, vod, events, users, system reboot, system upgrade) needs the gateway up.

Envelopes — parse .ok on CLI, .status on gateway:

SurfaceSuccessError
CLI{ok:true, command, protocol, data}{ok:false, error:{code, message, retryable}} (stderr)
CLI batch{command, summary, results:[{camera, host, ok, data, error}]}per-target ok
Gateway /api{status:"success", data}{status:"error", code, message} (HTTP status == code)

references/ has topic-scoped recipe files. Must load only the one matching your task — references/index.md is the router. references/troubleshooting.md has diagnosis decision trees for failures.

Ground Truth (Binding)

Forbidden stating or implying as factual: device reachability, login state, configuration values, capabilities, detection/AI types, event counts, VOD file existence, user accounts, or any observable camera state — except when derived from the JSON output of an executed reolink-cli command or gateway POST /api response within the current task.

Forbidden: demo-style lists, guessed layouts, synthetic values, fake success after errors/timeouts, prose/JSON mimicking CLI output without a real response, or "the device has X" when no get/info/inventory/capabilities has run.

Must when data is missing: state what's missing and the cause (no camera registered, gateway down, auth failed, device offline), then take one concrete recovery step (from Error Recovery table) or ask a single clarifying question. Forbidden padding with plausible-sounding defaults.

Intent Interpretation

Resolve ambiguity before picking a command. If still unclear, ask with options.

User saysCommandWhy / caveat
add a userusers addDevice account, NOT a CLI camera entry
add a cameradevice addCLI camera entry (~/.config/reolink-cli/aliases.toml), NOT a device account
change passwordusers passwd (default) OR device update (CLI creds)Ask which
blinking lightlight statusledBody LED
night visionlight irInvisible IR LED
spotlightlight spotlight (manual) OR light whiteled (alarm-triggered)Same bulb, different wiring
too darkimage tune --bright first, check light ir get if nightImage tuning, not spotlight
upside downimage flip set --flipVertical
mirroredimage flip set --mirrorHorizontal
maskprivacy maskRead current regions first
renameosd set --name (on-screen) OR config set device-name (app)Ask which
restart / rebootsystem reboot30–60s offline
upgrade / flash firmware / OTAsystem upgrade <file.pak>⚠️ bricking risk. Match model+hw_ver via info; GATEWAY reads the LOCAL file; auto windowed/stop-and-wait. Detail in references/admin.md
factory reset— standalone; system upgrade --factory-reset resets during a flashStandalone reset not in CLI; ask user to use Reolink app
kick offusers remove (permanent) OR users passwd + system reboot (evict session)Active sessions survive users remove until reboot
snapshotsnapshot [--file PATH]JPEG. Parent directories are auto-created by the binary (since v0.5.0) — do NOT pre-mkdir -p the destination. Same for vod download and preview capture --file.
slow / how long / time taken / performance / latency / benchmarkbenchmark [--iterations N] [--phases ...] [--reuse-session]Per-phase p50/p95/p99: connect, login, info, snapshot. --reuse-session for warm-path. Detail in references/index.md.
cpu / memory / device load / is the camera overloaded / stutteringconfig get performanceLive 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 / VLCstream 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 Ndevice 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 pauseNothing — 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, infochannel.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 lensinfo 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 livepreview play (opens ffplay window)DEFAULT to play, not capture. User wants a live window, not a file.
record N seconds / save a clip / save itpreview capture --packets ≈N×25Only when user explicitly says save / record / export. Packet count, not seconds; ~25 fps main, ~10 fps sub
60-second preview (ambiguous!)If user says "preview for N seconds/minutes" → preview play --packets N×fps. If user says "record N seconds" → preview capture. Ask if truly unclear.Default to play when the verb is "preview/watch", capture only when the verb is "record/save/export"
volumeaudio volume set (master)Ask if they mean mute: audio mute
mute alarmaudio muteAlarm audio only, not preview
siren / alarm sound / sound the alarm / scare them off / make it beepaudio siren play [--duration N | --times N] [--wait]Loud + side-effectful — confirm first. cmd 263 manual ring-down (the app's siren button); device self-stops after --duration s, or audio siren stop. --wait holds for the duration then stops explicitly and reports stoppedAt + stopAcknowledged — use it when you must be able to say the siren stopped, not just that it started. A 400 from a battery camera behind a hub is not evidence about the hardware — a sleeping child answers 400 exactly like an unimplemented command. (Separately: on one Home Hub 2 the siren was measured not to sound at all even when the command succeeds — see verified: false below. Never claim it sounded.) The gateway retries (measured: a camera idle >2 min needs 3 attempts, ~1.6 s), and attempts in the answer tells you which try got through. If it still fails, infochannel.loginState = standby explains it — but that field lags, so it is for explaining a failure, not for checking before one. Never tell a user their camera lacks a feature on the strength of a 400; capabilities is what answers that. Same rule for light spotlight. "siren when motion is detected" is different — that is audio siren task linkage (often 405 on newer models).
arm / disarm / away / at home / i'm leaving / scene mode / stop recording while i'm inscene set <id> (scene list for the ids, scene set --schedule to hand back to the timetable)Hub/NVR only. One switch re-arms every channel — it does not touch per-camera detection config. scene show first: currentSceneId 0 means the timetable is driving. Detail in references/scene-mode.md.
resolution / bitrate / fps / frame rate / h265 / h264 / stream quality / make the stream smallerencode set --stream main|sub|third ...Run encode capability first — a value the device does not advertise comes back as a bare 400. stream url is unrelated (that only builds a playback URL).
person detectiondetect ai --type personCheck device inventory --capabilities first
vehicledetect ai --type vehicleNot all models
turn on recordingrecord schedule set --enable
record config / recording params / pre-record seconds / clip sizerecord config getRead-only; cycle / pre/post record / package time
SD card / storage card / capacity / free spacestorage statusRead-only; totalGB / remainGB / formatted / mounted
manual record / record now / start recordingNot supported on Reolink IPCs; fall back to record schedule set --enable/--disable
download yesterday / download recordingvod search --since 24hvod download NAME
download 09:45–09:55 / just that clip / a time rangevod download --from 2026-09-02T09:45:00 --to 2026-09-02T09:55:00Camera-side cut — do NOT download the whole hour and trim
any alarms / any alarms todayevents query --since 24hRequires 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 detectedaudio talk (see references/voice-alert.md)PCM16 LE mono only; needs capabilities.audioTalk=1
detect X do Y / automation / event trigger / detect-then-doMust 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 recentlyevents 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.
watch this camera for the next 30 minutes / temporary rule / bounded monitoring taskadd a rule with expires_at = "<RFC 3339 or Unix second>", events monitor reload, then read back with events monitor history --rule NAMEAbsolute time only — a relative "30m" would restart its countdown on every reload. Expiry stops the rule firing; it does not rewrite the rules file, and captured history is kept.
apply edited rules / reload rulesevents monitor reload (Unix SIGHUP)Re-reads TOML without dropping cursor. Invalid TOML keeps old rules + logs warn. On Windows, stop + run again — SIGHUP is Unix-only.
what can it do / what features / what's supported / what can this CLI do / what's newreolink-cli features (add --output text for human view)Lists installed commands + highlights since v0.2.8 + XDG paths. Must run this first to gate which subcommands exist on the user's binary
how much disk used / cache size / cache usagereolink-cli cache statusInspect ~/.cache/reolink-cli/ by category (snapshots/audio/captures/downloads/logs)
current status / how are things / system health / overview / dashboardreolink-cli statusFleet + gateway + events-monitor + cache. --camera X to include recent events.
is the gateway running / gateway statusreolink-cli gateway status500ms TCP probe; on [DOWN] prints exact gateway start to run.
check / diagnose / doctor / what's wrong / sanity checkreolink-cli doctor9 offline checks (binary, dirs, config, registry, perms, gateway TCP). Use FIRST when user says "broken".
tab completion / shell completionreolink-cli completions {bash|zsh|fish|powershell|elvish} > <path>Re-run after self-update.
first-time setup / configure camera / set up reolink / how to use after install / how to get startedreolink-cli config init then device addconfig init writes default gateway addr; device add <alias> --host <ip> --user admin registers the first camera (password via prompt or --password-stdin). Then --alias <name> login to verify.
uninstall / delete / remove reolinkreolink-cli setup --uninstall --purge --no-interactiveConfirm with the user before running this — --purge deletes their camera registry and cannot be undone. Without a terminal on stdin — which is every command you launch — the uninstall refuses unless --no-interactive is present; that flag is the declaration that the deletion was intended, so do not add it reflexively to make an error go away. Removes binaries + config/cache/state + cross-agent skill dirs + Claude Code plugin registry. Drop --purge to keep config/cache/state (alias list preserved) — there is no --keep-config flag. The agent skill dirs are global (~/.claude/skills/…, ~/.agents/skills/…): they are removed regardless of REOLINK_PREFIX, so uninstalling one copy of a side-by-side install unlinks the skill for both. If the customer still has the extracted tarball, ./uninstall.sh in there is the symmetric alternative (it forwards straight to this same command; the uninstall flow only honours --purge, --no-interactive and --prefix). (Note: npx skills remove reolink-cli is rarely needed — reolink-cli setup --uninstall already wipes the agent skill dirs.)
skill stale / plugin cache stale / refresh skillreolink-cli plugin refreshAuto-detects agent (Claude Code / Codex / Cursor / Copilot / Gemini). Run when features reports cache_state != in_sync.
clean up / clear cache / delete old snapshots / clean cachecache clean [--older-than 7d] [--category X] [--apply]Dry-run by default — always preview first, run --apply only after the user sees the list

Workflow

Default: just run the command. The gateway daemon caches auth across CLI invocations, so the agent doesn't pay ping+login per command. Most user intents map to one CLI call (info, get, apply, snapshot, stream url, …).

Forbidden pre-fetching <subcmd> --help to "check what flags exist." The Intent Interpretation and Command Reference sections below, plus references/<topic>.md, already list every subcommand and its key flags; reolink-cli features enumerates the installed surface at runtime. Must dispatch the operative command directly. Only read --help if the command actually errored with an unknown-flag clap message. Each unnecessary --help round adds one full agent turn (~2–3 s of perceived user latency) for zero information gain — the user feels it, the CLI doesn't.

Must batch a single user intent into a single shell invocation when it needs multiple CLI commands. For sequences like "PTZ full sweep" (right→down→left→up→stop), "snapshot before/after a move", "info + capabilities + storage status", or "health check + benchmark" / "doctor + benchmark" pairs, chain them with && (or ; if you want continue-on-error) inside one Bash call — do not spawn N separate tool calls. Each extra tool call adds one full agent turn (~2–3 s). N=5 commands as one Bash = ~3 s perceived; N=5 commands as five tool calls = ~12 s perceived. The rule applies even when the commands are semantically distinct (e.g. doctor is local, benchmark hits the device) — the user asked for "both", so dispatch both in one shell line: reolink-cli doctor && reolink-cli --camera X benchmark --iterations 3. Independent prep checks for unrelated devices can still be parallel tool calls; the rule is about one intent → one invocation.

MCP server is the fastest path for repeated calls. reolink-cli mcp-server speaks JSON-RPC 2.0 over stdio (MCP protocol 2025-11-25, ~39 tools): identity (camera_{ping,login,info,capabilities,discover}), PTZ (camera_ptz_{move,stop,presets,preset_goto,preset_set,preset_delete}), light (camera_light_{ir,statusled}_{get,set}, camera_light_spotlight_set), audio (camera_audio_{volume_get,volume_set,mute}), detection (camera_detect_{motion,ai}_{get,set}), recording/storage (camera_record_config_get, camera_storage_status, camera_vod_search), events (camera_events_query), image/OSD (camera_image_{flip,tune}_get, camera_image_flip_set, camera_osd_{get,set}), users (camera_users_list), system (camera_system_reboot), plus camera_config_{get,set}, camera_preview_capture, camera_snapshot (JPEG to a file), and camera_raw. Benchmarked locally: 5 sequential camera_info calls take 52 ms via MCP vs 108 ms via 5 Bash spawns — ~52% faster, saving ~11 ms per call by avoiding process startup. When to suggest MCP wire-up: the user is going to do >3 ops in one conversation, or runs the agent in a tight loop. How to wire it in Claude Code (claude.json or settings):

"mcpServers": {
  "reolink-cli": {
    "command": "reolink-cli",
    "args": ["mcp-server"],
    "env": { "REOLINK_GATEWAY_ADDR": "127.0.0.1:9000" }
  }
}

Pass alias, host, or uid per tool call to target a specific camera; the server validates input schemas and returns {structuredContent, isError} payloads. Caveat: the gateway must be running separately (MCP routes through it the same way the CLI does).

apply recipes are one-shot. For "set X to Y" intents, the *** apply subcommand does get → compare → (skip if idempotent) → set → verify internally and returns {before, after, changed, verified}. Forbidden running get → set → get manually, and forbidden running a separate get after an apply — the verify is already inside.

RecipeReplaces
light ir apply --state auto|on|offmanual ir get/set/get
image flip apply [--flip|--no-flip] [--mirror|--no-mirror]manual flip get/set/get
osd apply [--name] [--datetime] [--name-overlay]manual osd get/set/get
detect motion apply [--enable] [--sensitivity N]manual motion get/set/get
detect ai apply --type T [--sensitivity N]manual ai get/set/get

ping and login are NOT pre-steps. They are diagnostics. The first command you actually need (info, get, apply, snapshot, …) does its own connect + auth via the gateway daemon. If that command's JSON has error.code == "auth_required", re-run login once and retry. If it has error.code == "connection_refused" / "timeout" / "no_route", that IS the same signal ping would give — no need to run ping again. Forbidden running ping followed by login followed by the real command on every turn; that's 3 round-trips for the work of 1. Only call ping standalone when the user explicitly asks "is camera X reachable?" or "why can't I connect?".

Verify only when needed. Re-get after a write only on capability-gated config SETs that can silently keep the old state (light ir/whiteled/spotlight set, image flip/tune set, audio config, detect motion/ai set, osd set, encode set, generic config set). Forbidden verifying after apply (already verified), ptz move/stop/preset, system reboot, system upgrade (confirm via info after the device reboots, not a re-get), snapshot, preview *, stream url, audio mute/unmute/talk, audio siren play/stop (a one-shot action, nothing to re-read), vod download, users passwd, privacy mask set. Full table in references/troubleshooting.md#verify-rules.

Other safety rules (kept verbatim — these break things if violated):

  • Target selectors are global options (pre-subcommand). Forbidden positional. Priority: --camera > --host > --uid > env. Batch: --tag, --cameras A,B, --all-devices. Must pass --channel N for NVR.
  • Credential safety: Forbidden --password PLAIN on argv. Must use --camera <name> (from aliases.toml 0600), REOLINK_PASSWORD env, or --password-stdin. If no camera registered and op isn't trivially read-only, ask user to run device add first.
  • Stored passwords are encrypted (RLENC1:…, AES-256-GCM) with the key in credentials.key beside the file that holds the password — beside aliases.toml in every layout, including a registry redirected with --cameras-file/REOLINK_CAMERAS_FILE, and beside a redirected config.toml if a password lives there. Separate registry files therefore have separate keys. A plaintext config is converted automatically on first use. Do not try to read a password out of aliases.toml — it is ciphertext, and there is no command that reveals it. Backing up or moving a config means copying the file and the credentials.key next to it as a pair, from the same directory; with only one of them the passwords are unrecoverable and must be re-entered via device update <camera> --password-stdin. If a command reports a password that "cannot be decrypted", the key file is missing or mismatched — that is not a wrong-password problem, so do not retry with guesses. (Installs from before 0.12.4 kept a single key in the default config directory; it is still read as a fallback until the next write moves it beside its file.)
  • Pre-write read: Must get current value before any write that's NOT covered by an apply recipe; confirm side effects from the table below; verify NVR channel.
  • Protocol: leave it unset — it defaults to v20, which the whole supported fleet speaks. A v30 camera declares protocol = "v30" in its entry (or --protocol v30 for one call). --protocol auto asks the gateway to detect it from the device's response magic and costs an extra TCP round trip; use it only to re-detect a camera whose entry names the wrong protocol.
  • Battery devices on LAN (transparent wake): when --uid is given without --host, the gateway automatically runs the BC3.0 §F.2 wake handshake on UDP 2026 before login (in customer / no-P2P builds). Forbidden calling reolink-cli wake as a precondition — it is a hidden diagnostic command. Must just call info / preview / ptz / etc. with --uid and accept that the first call may take 2–8 s while the device boots; subsequent calls within the gateway session TTL reuse the live session and have no wake overhead.
  • Events monitor exclusivity: when intent pairs trigger + action ("announce when a person is detected", "car detected → log"), Must use events monitor init/check/run with a TOML rule. Forbidden writing while true; do events query…; done shell loops — the rule engine already has debounce, parallel dispatch, SIGTERM draining, cursor bootstrapping, action-timeout, retry. See references/event-monitor.md.

Global Options

--host/--uid/--camera/--cameras A,B/--tag/--all-devices, --user/--password/--channel/--view, --protocol auto, --output json|text, --gateway-addr HOST:PORT, --config-file/--cameras-file. Env equivalents: REOLINK_HOST/UID/ALIAS/USER/PASSWORD/CHANNEL/VIEW/PROTOCOL/GATEWAY_ADDR/CONFIG_FILE/ALIASES_FILE. Run reolink-cli --help or <subcmd> --help for exact flag shapes.

Batch Operations

  • Must keep the same selector throughout a workflow — the batch framework handles fan-out.
  • Forbidden enumerating with --tag then switching to --camera inside the same workflow.
  • Batch output has per-target ok; a single failure doesn't fail the batch. Must check summary.failed > 0 and iterate results[].

Degenerate case: when a batch selector (--tag X, --cameras A, --all-devices) resolves to exactly one device, the CLI emits the single-target envelope ({ok, command, protocol, data}), not the batch report. Must test "summary" in response to detect which shape you got. Forbidden assuming results[] exists.

Error Recovery

ErrorCauseNext step
reolink-cli: command not foundBinary not installed (installing the skill does not install the binary)Run the install snippet in references/setup.mdInstall the reolink-cli binary, then retry
gateway connect failed: Connection refusedGateway not runningreolink-cli gateway start --addr 127.0.0.1:9000 & then retry
ping returns reachable:false, reason:"timeout"IP not routable (cross-subnet / VPN not up / host off / firewall)Tell user: verify VPN / check subnet; do not try login — it would hit the same timeout
ping returns reachable:false, reason:"refused"Host reachable but port 9000 closedWrong port / wrong IP / gateway service not running on device
ping returns reachable:false, reason:"no_route"No routing entry at allCheck netstat -rn / VPN
ping returns skipped:true, reason:"uid only..."UID-only targetExpected — proceed directly to login
device unreachable (retryable)Net drop / device off / wrong portRe-ping; check sibling
auth required / invalid credentialsWrong credsdevice update; admin-reset via Reolink app if locked out
invalid paramsCaller bugFix request, don't retry
unsupportedFirmware lacks featuredevice inventory --capabilities to confirm; skip
Connection reset after system rebootExpectedCLI swallows this one; re-ping after 30–60s
Remote (UID) login timeoutFirst-connect warm-upRetry with --timeout-secs 15+
wake timed out after 3 attempts: no response from device with uid XBattery device not reachable on the local broadcast domainVerify device is on the same /24 as the gateway host, UDP 2026 not firewalled, device not depleted; do not retry tightly — each attempt already takes ~3 s and the device may genuinely be off-network
device acknowledged wake (booting) but did not report ready within 30sMCU woke but Linux main failed to come upPhysical-side issue (battery low, firmware stuck); user must check the device
no active session (gateway)Token expiredRe-login via set auth.login
HTTP 401 (gateway)Missing Authorization: BearerAdd header
HTTP 403 (gateway)Token invalid/expiredRe-login
HTTP 410 on /api/login or /api/requestOld v0.1.1 endpointUse POST /api + Bearer
users remove admin failsDevice refuses removing last adminDon't
users passwd permission errorNon-admin changing someone else'sLog in as admin

Command Reference

Signatures only — run <cmd> --help for flag details; see references/<topic>.md for per-feature examples.

Discovery / Registry: discover, device list|resolve|show|add|update|remove|import|inventory|analyze|expand, config init

NVR multi-channel: device expand <nvr-name> [--yes | --names A,B,C] [--drop-parent]. RLN-series only; one entry per channel auto-tagged with parent name.

Identity: ping, login, info, capabilities

Config paths (config get/set PATH with merge semantics): 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]. --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.

Doctor: doctor — 9 sanity checks (binary, dirs, config.toml, registry, perms 0600, legacy cleanup, gateway TCP). Non-zero exit on fail. Use first for "broken" reports.

Completions: completions {bash|zsh|fish|powershell|elvish} — emits to stdout; redirect into shell's completion dir. Re-run after self-update.

Gateway status: gateway status — 500ms TCP probe of the resolved gateway-addr. Text shows [LISTENING] / [DOWN] / [UNCONFIGURED] + the gateway start command on down.

Gateway log files (since 0.12.5): the gateway always writes a compact single-line log to <cache-dir>/logs/gateway-<pid>.log (~/.cache/reolink-cli/logs/ on Linux, ~/Library/Caches/reolink-cli/logs/ on macOS, %LOCALAPPDATA%\reolink-cli\logs\ on Windows) at info level; files older than 14 days are pruned at startup, and cache clean --category logs manages them like any other cache category. To collect evidence after a failed run: zip the logs/ directory — no pre-configuration needed. For deep traces re-run with RUST_LOG=debug (adds per-round LAN probe results, every TCP connect attempt with address+outcome, uid-cache hits with age). REOLINK_LOG_FILE=off disables the file; REOLINK_LOG_FILE=<path> redirects it (user-managed, no pruning). Credentials never appear in logs; entries do contain hosts/UIDs/SSIDs, so redact before posting publicly.

Stream URLs: stream url [--kind rtsp,rtmp,flv] [--stream main,sub,ext] [--with-auth] — defaults to rtsp main. --with-auth embeds creds (use only when user wants pasteable URL); without it ships sibling user/password JSON fields. NVR: device expand then --tag <nvr> stream url. RTSP path is /Preview_{NN}_{main|sub|ext}. Detail in references/media.md.

Light: light {ir|statusled} {get|set --state auto|on|off}, light ir apply --state auto|on|off (recipe; prefer for "set X"), light spotlight set --enable|--disable [--duration], light whiteled {get|set [--enable|--disable] [--brightness 0-100] [--alarm-mode] [--detect-type T,...]}. Caveat: E-series can ok:true on light ir set while silently keeping old state — apply recipe surfaces this in verified:false; check device inventory --capabilities if write ignored.

Image: image flip {get|set|apply [--flip|--no-flip] [--mirror|--no-mirror]} (apply is the idempotent recipe), image tune {get|set [--bright N] [--contrast N] [--saturation N] [--hue N] [--sharpen N]} — all 0–255, 128 = neutral. If a user says "60%" that's ~160.

OSD: osd {get|set|apply [--name NAME] [--datetime|--no-datetime] [--name-overlay|--no-name-overlay]} (apply is the idempotent recipe)

System: system reboot; system upgrade <file.pak> [--factory-reset] [-y] — flash firmware (gateway reads the LOCAL file; ⚠️ bricking risk, match model+hw_ver; auto windowed/stop-and-wait by capability; detail in references/admin.md)

Users (device accounts): users list, users add NAME --level admin|user, users remove NAME, users passwd NAME. Password via TTY prompt or --password-stdin — never --password PLAIN. Name + password each 1–31 chars. Non-admin users can only change their own password.

Audio: audio config {get|set}, audio volume {get|set LEVEL} (0–100), audio mute, audio unmute, audio replies, audio talk {--file PATH | --stdin} [--sample-rate N]. audio volume is master; audio config.volume is per-profile — ask which. audio talk pushes PCM16 LE mono to camera speaker via talkback (cmd 201 open + 202 stream). Gate on capabilities.audioTalk=1.

Siren: audio siren play [--duration 1-600 | --times 1-100] [--wait] (default 2 repetitions ≈ 6s; device self-stops after the last one), audio siren stop (silence now). The device sounds in whole repetitions of ~3.2s, so --duration rounds up to --times and the answer reports both, plus speakerVolumeif the user says they heard nothing, read that first (a measured case sat at 7/100 and was inaudible; the command succeeded regardless). Up to 0.14.3 the siren never actually sounded — the CLI sent playMode 1 (by duration), which this firmware accepts with a 200 and ignores; playMode 0 (by times) is the one that makes noise, confirmed against the camera's own microphone. --wait blocks for the duration, then sends an explicit stop and adds waited/stoppedAt/stopAcknowledged to the answer; the firmware timer is still sent, so a killed CLI does not leave the siren running. Without --wait the answer is accepted: true, verified: false and nothing more — v20 has no GET for siren state, so never report that the siren sounded on the strength of it. This is the app's manual-siren button = v20 cmd 263 NET_MANUAL_RING_DOWN (<audioPlayInfo> continuous/switch mode), verified on real RLC-823/810. Gate on capabilities / JSON GetAbility.supportAudioAlarm. Also audio siren task {get|set --on|--off} (which alarms trigger it) + audio siren enable {get|set [--on|--off] [--channel-on|--channel-off]}two switches: device-wide mainSwitch and a per-channel channelMask bit. A hub can report master-on with every channel bit zero; --on alone cannot fix that, which is why --channel-on exists — v20 cmds 306/307, 316/317, but this whole v3.x generation returns 405 for them; only manual play/stop is universal. A sounding device pushes cmd 547 → events stream shows siren.on/siren.off (no GET for siren state). NB: do NOT confuse with cmd 349 (play a stored audio file) — it 400s on cameras with an empty audio store, i.e. almost all of them.

Scene mode (hub arming profiles): scene show (active scene + which task bits this hub honours + the ids), scene list (every scene with its per-channel tasks), scene status, scene set <id> / scene set --schedule, scene edit <id> [--tasks record,ftp,email,push,audio,linkage,speaker,track] [--channel N] [--iot-tasks linkage,device] [--name ...] [--delay N], scene schedule get|set --scene <id> [--days 0,1] [--from H] [--to H], scene alarm get|set --task REC --types people,vehicle [--channel N], scene options [--key-enable|--key-disable] [--private-mode|--no-private-mode].

A scene is a named set of per-channel task bits, so switching scenes re-arms the whole hub at once — that is the app's Home / Away / Disarm control. audio in the task list is the siren, not the microphone. Scene id 0 is not a scene: it hands control to the weekly schedule, and scene show reports that as followSchedule: true. --tasks replaces the scene's set rather than adding to it, and without --channel it applies to every channel in the scene. The schedule is 7 days × 2 half-hour slots × 24 hours; --days takes the device's own 0-6 numbering.

Encoder: encode get (all three streams), encode capability (what the device accepts — read this before setting), encode set --stream main|sub|third [--resolution WxH] [--fps N] [--bitrate KBPS] [--codec h264|h265] [--rate-control vbr|cbr] [--profile default|baseline|main|high] [--gop N] [--smart-h265|--no-smart-h265] [--smart-h265-level 0-2]. The wire SET (cmd 57) rewrites all three streams at once, so the CLI always re-reads first and patches only the flags you pass. Changing resolution restarts the stream — any live preview/RTSP consumer drops. GOP is pinned by firmware when H.265+ is on (level 0/1 → 2, level 2 → 4), so --gop is ignored in that case.

Privacy: privacy mask {get|set [--enable|--disable] [--json JSON]}. Get first to see current regions (device-pixel {x,y,width,height}).

PTZ: ptz move DIR [--speed 1-64] [--duration-ms], ptz stop, ptz preset {list|goto ID|set ID NAME|delete ID}, ptz {zoom|focus} {get|set --pos N}, ptz focus auto --enable|--disable, ptz patrol {list|start|stop}, ptz guard {get|set|snapshot|goto}, ptz autotrack {get|set [--enable|--disable] [--mode N] [--detect-type T,...] [--priority T,...]}. Directions: left|right|up|down|left-up|left-down|right-up|right-down. Speed 1–64 (not %). If user says "speed 50" ask if it means % or literal.

Recording: record schedule {get|set [--enable|--disable] [--fps 1-15] [--pre-time] [--plan-type daily|weekly] [--week-table BITMAP] [--start-hour H --start-min M --end-hour H --end-min M]}, record config get (read-only: cycle / pre-record / post-record / packageTime). --week-table is 7-bit bitmap (Mon=bit0 … Sun=bit6); weekdays=31, Mon-Sat=63, all=127. Manual start/stop not supported — cmd 277/278/587/588 return 405.

Storage: storage status (read-only SD/HDD capacity + mount state). Forbidden format/init ops — direct users to the Reolink app if they need to format.

VOD: vod search [--from ISO --to ISO | --since DURATION] [--type T,...] [--stream main|sub] [--limit N], vod download NAME [-o FILE], vod download --from ISO --to ISO (time range, the camera cuts it — one request per recording segment, joined; output is an Annex-B .hevc/.h264 elementary stream; add --audio clip.aac for the sound — it is a second request and a second file, ADTS AAC, so ffmpeg -i clip.hevc -i clip.aac -c copy clip.mp4 remuxes losslessly; unsupported models answer 400). Time must be naive local ISO (YYYY-MM-DDTHH:MM:SS, no TZ, no ms). Cross-month windows are handled for you (the gateway splits at month boundaries and merges); limit applies to the merged list, and truncated tells you it cut short. Filenames case-sensitive. Types: manual|sched|io|md|people|vehicle|face|dog_cat|visitor|other|package

Detection: detect motion {get|set|apply [--enable|--disable] [--sensitivity 0-100] [--use-pir|--disable-pir]}, detect ai {get|set|apply} --type TYPE [--sensitivity 0-100] [--stay-time SECS]. apply is the idempotent recipe — prefer it for "set sensitivity to N"-style intent. AI types: person|vehicle|dog_cat|package|cry (subset varies by model).

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,...] | 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.

Wake (diagnostic, hidden): wake --uid <UID> [--host <HOST>] — send BC3.0 §F.2 UDP wake packet without logging in. Broadcast if --host is omitted. Marked hide = true in top-level help — appears only when explicitly queried (wake --help). Normal workflow does not need this; info/login/ptz/etc. auto-wake battery devices via the gateway. Forbidden calling wake as a pre-step before any of those commands.

Raw: raw CMD [--body-xml|--ext-xml|--body-file|--ext-file].

Self-description: reolink-cli features [--output json|text] — prints the installed version, the list of subcommands this binary supports, feature highlights with their since version, and the XDG file layout. Must run this (or rely on AGENTS.md Part 5) before dispatching subcommands you haven't confirmed exist on the user's binary — skill docs track master, the binary may lag.

Cache management: reolink-cli cache {status|clean}. status reports per-category size + oldest/newest file timestamps under ~/.cache/reolink-cli/. clean [--older-than 7d] [--category audio|snapshots|captures|downloads|logs|all] [--apply] lists candidates by default; add --apply to delete. --older-than 0 matches every file. Empty date-bucket subdirs under snapshots/ are auto-pruned.

Gateway HTTP API

Single entry POST /api with Authorization: Bearer <token> and body {action, path, params}. Login is itself a call: {action:"set", path:"auth.login", params:{username, password, host, protocol?, channel?}}{status:"success", data:{token, info}}. Streaming endpoints (/api/events, /api/preview/video, /api/snapshot, /api/vod/download) take ?token= in the query for browser-native elements; the token supplies the device credentials server-side, so never put user/password in a URL (requests without a valid token are rejected 401/403). See references/gateway-http.md for curl recipes and the full spec.

Side-effectful commands

Must confirm with the user before running any of these. Forbidden executing them opportunistically or as part of a read-only inspection flow:

CommandSide effect
`ptz movegoto
light spotlight --enable, light whiteled --enableBright visible light
audio volume set (high), audio replies triggersLoud output
audio siren playVery loud alarm audible outside the property
scene set / scene editRe-arms every channel on the hub at once. scene edit --tasks replaces the task set, so a scene edited down to nothing records and notifies nothing (references/scene-mode.md)
encode set (resolution change)Restarts the encoder — live preview / RTSP / NVR consumers drop
privacy mask --disableUnmasks previously hidden regions
system reboot30–60s downtime; aborts in-flight
`users removepasswd` (others)
record schedule --disableNo new recordings
`detect motionai --disable`
cache clean --applyPermanent file deletion under ~/.cache/reolink-cli/ — always preview with a dry-run first

Gotchas

  • NVR: Must pass --channel N; wrong channel silently returns wrong config. Must verify via device resolve when uncertain.
  • Capability variance: detect ai --type support differs per model. Battery cams may be person only. Must run device inventory --capabilities before scripting AI calls across devices.
  • Empty {} response ≠ error on paths the device doesn't expose — do not treat as failure.
  • Argv wins over env when both are set for --password.
  • Remote (UID) first connect is 10–15 s; Must bump --timeout-secs 15+ when first-connecting a UID target.
  • preview capture writes BC framing, not raw H.264 — ffplay needs -f hevc|-f h264. preview play already handles that.
  • Forbidden running events query / events stream without --gateway-addr — it will fail fast with a clear error, but don't attempt it.
  • Gateway tokens don't auto-refresh. Must re-login after long pauses or on HTTP 403.
  • Batch cannot prompt mid-run. Must register cameras (with stored creds) before any batch operation.
  • Siren = audio siren play (cmd 263), never anything about "audio files". The manual siren is a one-shot ring-down; a device 400 here almost always means the model genuinely lacks a siren, not a bad call. audio siren task/enable (the alarm-linkage config) is 405 on most current v3.x models — if config-get 405s, manual play/stop still works; don't conclude "no siren".
  • encode set is a full-stream rewrite (cmd 57). The CLI already does GET→patch→SET so untouched streams are safe, but a value the device doesn't list is rejected with a bare 400 — Must run encode capability first and pick a listed resolution/fps/bitrate. Changing resolution restarts the encoder, dropping any live preview/RTSP/NVR consumer.

File locations (XDG layout)

Single parent per category — easy to purge, easy to find. Must prefer these paths in rules, examples, and command suggestions. Forbidden scattering artifacts across /tmp/*.pcm, /var/reolink/..., /etc/reolink/....

CategoryPathManaged by
Persistent config (CLI, aliases, monitor rules)~/.config/reolink-cli/CLI (config init, device add, events monitor init) — 0600 perms
App state (pidfile, cursor, debounce, trigger history)~/.local/state/reolink-cli/events monitor daemon — monitor-state.json + monitor-history.jsonl + monitor.pid
Disposable artifacts (snapshots, PCM clips, captures, VOD)~/.cache/reolink-cli/{audio,snapshots/<date>,captures,downloads}/user / rule authors — Must use this prefix
Claude Code plugin cache~/.claude/plugins/cache/reolink-cli/Claude Code (/plugin update refreshes)

events monitor expands ~/ in path and pcm fields and auto-mkdir -p's the snapshot parent dir — safe to write rules with ~/.cache/reolink-cli/snapshots/{date}/... without pre-creating anything. Bulk purge: rm -rf ~/.cache/reolink-cli.

Launcher

Prefer the installed binary: reolink-cli …. Plugin-bundled: ${CLAUDE_PLUGIN_ROOT}/scripts/run-cli.{sh,cmd}.

References

Topic-scoped recipe files. Must load only the one matching your task; Forbidden reading the full set.

FileRead when
references/setup.mdDiscovery, registry, identity, generic config get/set
references/media.mdPreview / snapshot / VOD
references/controls.mdLight / image / OSD / audio / privacy
references/ptz.mdAny ptz …
references/detection.mdMotion / AI detection (detect motion, detect ai)
references/recording.mdRecord schedule + core config (record schedule, record config get)
references/storage.mdSD card / HDD read-only status (storage status)
references/notifications.mdMobile push (notify push)
references/events.mdGateway event bus — events query / events stream, event shape
references/event-monitor.mdevents monitor rule engine — TOML-driven automation (preferred over shell watchers)
references/voice-alert.mdDetection-triggered voice announcement via audio talk (shell-watcher legacy)
references/admin.mdDevice user accounts, system reboot, raw debug
references/gateway-http.mdGateway POST /api from curl or non-CLI clients
references/troubleshooting.mdSomething failed; or full intent map

Other docs (in the repo, not this skill):

  • docs/cli-reference.md — generated flag reference

Skills associés