@camstack/addon-provider-reolink 1.2.185 → 1.2.186

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.mjs CHANGED
@@ -5416,7 +5416,7 @@ var ZodIssueCode = {
5416
5416
  var ZodFirstPartyTypeKind;
5417
5417
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5418
5418
  //#endregion
5419
- //#region ../types/dist/sleep-BBF1iXUi.mjs
5419
+ //#region ../types/dist/sleep-DRqxtc0C.mjs
5420
5420
  /**
5421
5421
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5422
5422
  * window to float samples (D455).
@@ -8289,6 +8289,24 @@ function collectZodSecretPaths(schema, prefix = "") {
8289
8289
  }
8290
8290
  return [...new Set(paths)];
8291
8291
  }
8292
+ object({
8293
+ code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
8294
+ params: record(string(), union([string(), number()])).optional()
8295
+ });
8296
+ var CamstackError = class extends Error {
8297
+ i18nCode;
8298
+ i18nParams;
8299
+ constructor(code, params, englishMessage) {
8300
+ super(englishMessage);
8301
+ this.name = "CamstackError";
8302
+ this.i18nCode = code;
8303
+ this.i18nParams = params;
8304
+ }
8305
+ };
8306
+ /** `throw userError('device.not-found', { id }, 'Device 12 not found')` */
8307
+ function userError(code, params, englishFallback) {
8308
+ return new CamstackError(code, params, englishFallback);
8309
+ }
8292
8310
  /**
8293
8311
  * Promise-based timer helpers — used everywhere the codebase needs to
8294
8312
  * wait, back off, or schedule a retry. Before these helpers landed, each
@@ -8314,7 +8332,7 @@ function sleep$2(ms) {
8314
8332
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
8315
8333
  }
8316
8334
  //#endregion
8317
- //#region ../types/dist/err-msg-DX6i_MY4.mjs
8335
+ //#region ../types/dist/err-msg-DLerKiVO.mjs
8318
8336
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
8319
8337
  DeviceType["Camera"] = "camera";
8320
8338
  DeviceType["Hub"] = "hub";
@@ -8564,6 +8582,22 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
8564
8582
  DeviceRole["Nightvision"] = "nightvision";
8565
8583
  DeviceRole["PrivacyMask"] = "privacy-mask";
8566
8584
  DeviceRole["Doorbell"] = "doorbell";
8585
+ /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
8586
+ DeviceRole["CoverBlind"] = "cover-blind";
8587
+ /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
8588
+ DeviceRole["CoverShutter"] = "cover-shutter";
8589
+ /** Exterior awning. HA `device_class: awning`. */
8590
+ DeviceRole["CoverAwning"] = "cover-awning";
8591
+ /** Curtain / drape. HA `device_class: curtain`. */
8592
+ DeviceRole["CoverCurtain"] = "cover-curtain";
8593
+ /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
8594
+ DeviceRole["CoverGarage"] = "cover-garage";
8595
+ /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
8596
+ DeviceRole["CoverGate"] = "cover-gate";
8597
+ /** Door. HA `device_class: door`. A security actuator: exports gate it. */
8598
+ DeviceRole["CoverDoor"] = "cover-door";
8599
+ /** Window. HA `device_class: window`. */
8600
+ DeviceRole["CoverWindow"] = "cover-window";
8567
8601
  /** Virtual HA toggle (input_boolean.*) — distinguishable from a
8568
8602
  * real Switch device for UI rendering / export adapters. */
8569
8603
  DeviceRole["BinaryHelper"] = "binary-helper";
@@ -8634,7 +8668,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
8634
8668
  return DeviceRole;
8635
8669
  }({});
8636
8670
  //#endregion
8637
- //#region ../types/dist/composition-C-WpVYTX.mjs
8671
+ //#region ../types/dist/composition-EQ9Ok0Ee.mjs
8638
8672
  /**
8639
8673
  * The ffmpeg build every CamStack node downloads, per platform and arch.
8640
8674
  *
@@ -12862,6 +12896,177 @@ var ConvertResultSchema = object({
12862
12896
  files: array(string()).readonly()
12863
12897
  })).readonly()
12864
12898
  });
12899
+ _enum(["new-session", "step-up"]);
12900
+ /** The languages the hub's app landing pages are written in (D858). */
12901
+ var AppPageLangSchema = _enum(["en", "it"]);
12902
+ /** How a native app starts one login method (D858), as `auth.listLoginMethods` reports it. */
12903
+ var AppLoginPathSchema = discriminatedUnion("kind", [object({ kind: literal("native") }), object({
12904
+ kind: literal("browser"),
12905
+ startPath: string().startsWith("/")
12906
+ })]);
12907
+ /**
12908
+ * Device certificates (D855) — the wire contract shared by the hub, the admin
12909
+ * UI and the native viewer.
12910
+ *
12911
+ * A device (a phone) generates its OWN P-256 key pair in hardware (Secure
12912
+ * Enclave / Android Keystore / a non-extractable WebCrypto key). The private
12913
+ * key never travels. The hub signs the PUBLIC key into an X.509 leaf under a
12914
+ * dedicated device CA; the device later proves possession of the key by
12915
+ * signing a hub-issued nonce, and the hub answers with the same session a
12916
+ * password login opens.
12917
+ *
12918
+ * Everything on the wire is a small string (a PEM, a base64url signature):
12919
+ * D613 is about bulk bytes, and a certificate is ~600 bytes.
12920
+ */
12921
+ /** Signature algorithm of every device credential: ECDSA, curve P-256, SHA-256. */
12922
+ var DEVICE_CERT_SIGNATURE = "ecdsa-p256-sha256";
12923
+ /** First line of every signed message; a change of format bumps the version. */
12924
+ var DEVICE_SIGNATURE_MESSAGE_VERSION = "camstack-device-cert-v1";
12925
+ _enum([
12926
+ "login",
12927
+ "renew",
12928
+ "revoke"
12929
+ ]);
12930
+ var DevicePlatformSchema = _enum([
12931
+ "ios",
12932
+ "android",
12933
+ "web",
12934
+ "other"
12935
+ ]);
12936
+ var Base64UrlSchema = string().min(1).max(4096).regex(/^[A-Za-z0-9_-]+$/);
12937
+ var PemSchema = string().min(1).max(16384);
12938
+ /**
12939
+ * Why a device-certificate endpoint said no. Distinct from a transport / 5xx
12940
+ * failure: those carry no reason and mean "try again", never "the credential is
12941
+ * dead". The viewer switches on this exhaustively.
12942
+ */
12943
+ var DeviceRefusalReasonSchema = _enum([
12944
+ "revoked",
12945
+ "unknown-credential",
12946
+ "expired",
12947
+ "bad-signature",
12948
+ "nonce-invalid",
12949
+ "rate-limited",
12950
+ "invalid-certificate",
12951
+ "superseded",
12952
+ "user-gone",
12953
+ "unavailable",
12954
+ "code-invalid",
12955
+ "self-enrollment-disabled",
12956
+ "step-up-required",
12957
+ "bad-public-key"
12958
+ ]);
12959
+ var DeviceRefusalSchema = object({
12960
+ ok: literal(false),
12961
+ reason: DeviceRefusalReasonSchema,
12962
+ definitive: boolean(),
12963
+ retryAfterSeconds: number().optional()
12964
+ });
12965
+ object({
12966
+ code: string().min(1).max(256),
12967
+ /** SubjectPublicKeyInfo DER of the device's P-256 public key, base64url. */
12968
+ publicKeySpki: Base64UrlSchema,
12969
+ deviceName: string().min(1).max(80).optional(),
12970
+ platform: DevicePlatformSchema
12971
+ });
12972
+ discriminatedUnion("ok", [object({
12973
+ credentialId: string(),
12974
+ certPem: PemSchema,
12975
+ /** The device CA certificate the leaf chains to. */
12976
+ chainPem: PemSchema,
12977
+ notAfter: number(),
12978
+ user: object({
12979
+ id: string(),
12980
+ username: string()
12981
+ })
12982
+ }).extend({ ok: literal(true) }), DeviceRefusalSchema]);
12983
+ object({
12984
+ /** The single-use step-up proof (`auth.deviceEnrollProofFrom…`). */
12985
+ proof: string().min(1).max(512),
12986
+ publicKeySpki: Base64UrlSchema,
12987
+ deviceName: string().min(1).max(80),
12988
+ platform: DevicePlatformSchema
12989
+ });
12990
+ object({
12991
+ proof: string(),
12992
+ expiresAt: number()
12993
+ });
12994
+ discriminatedUnion("ok", [object({
12995
+ nonce: string(),
12996
+ /** The origin to put in the signed message. The app compares it to the hub it dialled. */
12997
+ origin: string(),
12998
+ expiresAt: number(),
12999
+ signature: literal(DEVICE_CERT_SIGNATURE)
13000
+ }).extend({ ok: literal(true) }), DeviceRefusalSchema]);
13001
+ object({
13002
+ certPem: PemSchema,
13003
+ nonce: string().min(1).max(256),
13004
+ /** ECDSA P-256 SHA-256 over {@link buildDeviceSignatureMessage}; raw r||s or DER, base64url. */
13005
+ signature: Base64UrlSchema
13006
+ });
13007
+ discriminatedUnion("ok", [object({
13008
+ credentialId: string(),
13009
+ certPem: PemSchema,
13010
+ chainPem: PemSchema,
13011
+ notAfter: number()
13012
+ }).extend({ ok: literal(true) }), DeviceRefusalSchema]);
13013
+ discriminatedUnion("ok", [object({
13014
+ ok: literal(true),
13015
+ credentialId: string(),
13016
+ sessionsEnded: number().int()
13017
+ }), DeviceRefusalSchema]);
13018
+ var DeviceCredentialStatusSchema = _enum([
13019
+ "active",
13020
+ "expired",
13021
+ "revoked"
13022
+ ]);
13023
+ var DeviceCredentialSchema = object({
13024
+ id: string(),
13025
+ userId: string(),
13026
+ username: string().nullable(),
13027
+ deviceName: string(),
13028
+ platform: DevicePlatformSchema,
13029
+ enrolledVia: _enum(["qr", "self"]),
13030
+ /** SHA-256 of the leaf certificate DER, colon-hex. */
13031
+ fingerprint: string(),
13032
+ issuedAt: number(),
13033
+ expiresAt: number(),
13034
+ lastUsedAt: number().nullable(),
13035
+ lastUsedFrom: string().nullable(),
13036
+ revokedAt: number().nullable(),
13037
+ /** `admin:<userId>` | `user:<userId>` | `device` | `user-deleted`. */
13038
+ revokedBy: string().nullable(),
13039
+ status: DeviceCredentialStatusSchema
13040
+ });
13041
+ var StartEnrollmentOutputSchema = object({
13042
+ /** Shown once; only its hash is kept. */
13043
+ code: string(),
13044
+ expiresAt: number(),
13045
+ /** `camstack://enroll?hub=…&code=…` — what the QR encodes. */
13046
+ enrollUrl: string(),
13047
+ /** The hub origin in the link, or `null` when the hub could not name one (the link then has no `hub`). */
13048
+ hubOrigin: string().nullable(),
13049
+ /** Every origin the apps may try (role `appConnections`). */
13050
+ hubOrigins: array(string())
13051
+ });
13052
+ object({
13053
+ kind: literal("device-certificate"),
13054
+ id: literal("device-certificate"),
13055
+ label: string(),
13056
+ stage: literal("primary"),
13057
+ /** tRPC paths under the hub's `auth` router, so a client names no literal. */
13058
+ challengePath: literal("auth.getDeviceChallenge"),
13059
+ revokePath: literal("auth.revokeOwnDeviceCertificate"),
13060
+ enrollSelfPath: literal("auth.enrollSelf"),
13061
+ loginPath: literal("auth.loginWithDeviceCertificate"),
13062
+ renewPath: literal("auth.renewDeviceCertificate"),
13063
+ signature: literal(DEVICE_CERT_SIGNATURE),
13064
+ messageVersion: literal(DEVICE_SIGNATURE_MESSAGE_VERSION),
13065
+ /** Which clients can execute it: a native app silently, a browser with a non-extractable WebCrypto key. */
13066
+ clients: array(_enum(["native", "web"])),
13067
+ /** The app executes it itself, silently (D858). */
13068
+ app: AppLoginPathSchema.nullable()
13069
+ });
12865
13070
  var ExpressionBindingSourceSchema = union([
12866
13071
  object({
12867
13072
  kind: literal("field").optional(),
@@ -13190,7 +13395,9 @@ var ConfigTabDeclarationSchema = object({
13190
13395
  var ConfigSectionWithValuesSchema = object({
13191
13396
  id: string(),
13192
13397
  title: string(),
13398
+ titleKey: string().optional(),
13193
13399
  description: string().optional(),
13400
+ descriptionKey: string().optional(),
13194
13401
  style: _enum(["card", "accordion"]).optional(),
13195
13402
  defaultCollapsed: boolean().optional(),
13196
13403
  columns: union([
@@ -15686,7 +15893,11 @@ var cameraStreamsCapability = {
15686
15893
  * See `RuntimeStateDurability`. Enforced by
15687
15894
  * `scripts/check-runtime-state-durability.ts`.
15688
15895
  */
15689
- durability: "session"
15896
+ durability: "session",
15897
+ /**
15898
+ * History (D850): none — `online` duplicates device-status.online (one authority); slot data is nested enum/string.
15899
+ */
15900
+ history: "none"
15690
15901
  };
15691
15902
  EventCategory.DoorbellOnPressed;
15692
15903
  var CompositionEventPlanSchema = object({
@@ -16518,7 +16729,345 @@ method(object({
16518
16729
  }), method(object({ refs: RefListSchema }), KeychainResolveResultSchema, { auth: "admin" }), method(object({ refs: RefListSchema }), object({ deleted: number().int() }), {
16519
16730
  kind: "mutation",
16520
16731
  auth: "admin"
16732
+ }), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
16733
+ kind: "mutation",
16734
+ auth: "admin"
16521
16735
  }), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
16736
+ /**
16737
+ * device-history — the ONE mechanism for numeric history (D850).
16738
+ *
16739
+ * A device value is recorded because its capability says so (the `history`
16740
+ * block of a `*.cap.ts`, captured hub-side from `DeviceStateChanged`); any
16741
+ * other series is DECLARED by the addon that produces it and fed with points
16742
+ * (`record`) or closed five-minute buckets (`recordBuckets`). Rollups 5m / 1h /
16743
+ * 1d are computed here and nowhere else. Storage is `history.db` (D851).
16744
+ *
16745
+ * Who the PRODUCER is: `declareSeries`, `record` and `recordBuckets` are owner
16746
+ * checked against the `callerAddonId` the transport stamps into a call that
16747
+ * crossed a process (never in an input schema, so a session cannot supply it).
16748
+ * A call with no stamp is refused `not-owner`. The methods are `admin` so a
16749
+ * non-admin session never reaches them; an addon's `ctx.api` call bypasses the
16750
+ * tier by design.
16751
+ */
16752
+ /** Whose history: a real device, or a cluster node (load, memory). */
16753
+ var DeviceHistorySubjectSchema = discriminatedUnion("kind", [object({
16754
+ kind: literal("device"),
16755
+ deviceId: number().int()
16756
+ }), object({
16757
+ kind: literal("node"),
16758
+ nodeId: string().min(1)
16759
+ })]);
16760
+ var DeviceHistoryKindSchema = _enum([
16761
+ "gauge",
16762
+ "counter",
16763
+ "boolean"
16764
+ ]);
16765
+ var DeviceHistoryResolutionSchema = _enum([
16766
+ "raw",
16767
+ "5m",
16768
+ "1h",
16769
+ "1d"
16770
+ ]);
16771
+ /** Why a producer's point or bucket was not stored. Counted, never silent (D391). */
16772
+ var DeviceHistoryRefusalReasonSchema = _enum([
16773
+ "undeclared",
16774
+ "not-owner",
16775
+ "opted-out",
16776
+ "not-a-real-device",
16777
+ "rate",
16778
+ "ingest-mismatch",
16779
+ "out-of-range"
16780
+ ]);
16781
+ var RefusalSchema = object({
16782
+ key: string(),
16783
+ reason: DeviceHistoryRefusalReasonSchema,
16784
+ count: number().int()
16785
+ });
16786
+ var IngestResultSchema = object({
16787
+ accepted: number().int(),
16788
+ refused: array(RefusalSchema).readonly()
16789
+ });
16790
+ var SeriesInfoSchema = object({
16791
+ key: string(),
16792
+ scope: string(),
16793
+ kind: DeviceHistoryKindSchema,
16794
+ /** `null` is unknown (a `unitFrom` series whose device does not state a unit right now), never a guess. */
16795
+ unit: string().nullable(),
16796
+ displayName: string(),
16797
+ source: _enum(["cap", "addon"]),
16798
+ firstTs: number(),
16799
+ lastTs: number()
16800
+ });
16801
+ var QueryPointSchema = object({
16802
+ /** Raw: the point's time. Rollup: the bucket start. */
16803
+ t: number(),
16804
+ samples: number().int(),
16805
+ /** `sum / samples`. */
16806
+ mean: number().nullable(),
16807
+ /** Time-weighted mean (`tw_sum / tw_ms`); `null` for bucket-ingested series. For a boolean: the fraction of time true. */
16808
+ twMean: number().nullable(),
16809
+ min: number(),
16810
+ max: number(),
16811
+ /** `null` is unknown, never zero (D393). */
16812
+ last: number().nullable()
16813
+ });
16814
+ var QuerySeriesSchema = object({
16815
+ scope: string(),
16816
+ /** The last point STRICTLY BEFORE `from` in the chosen tier, so a step chart can start at the held value. */
16817
+ before: object({
16818
+ t: number(),
16819
+ value: number().nullable()
16820
+ }).nullable(),
16821
+ points: array(QueryPointSchema).readonly()
16822
+ });
16823
+ /** The window part of a query, shared by the device and node forms. */
16824
+ var QueryWindowShape = {
16825
+ key: string(),
16826
+ scope: string().optional(),
16827
+ from: number(),
16828
+ to: number(),
16829
+ resolution: union([DeviceHistoryResolutionSchema, literal("auto")]).default("auto"),
16830
+ maxPoints: number().int().positive().max(5e3).default(1e3)
16831
+ };
16832
+ var QueryResultSchema = object({
16833
+ resolution: DeviceHistoryResolutionSchema,
16834
+ series: array(QuerySeriesSchema).readonly(),
16835
+ truncated: boolean()
16836
+ });
16837
+ var TierStatusSchema = object({
16838
+ tier: DeviceHistoryResolutionSchema,
16839
+ /** `null` until the first background count answered. */
16840
+ rows: number().int().nullable(),
16841
+ /** `rows` times a fixed per-row estimate — approximate, labelled so. */
16842
+ approxBytes: number().nullable(),
16843
+ countedAt: number().nullable()
16844
+ });
16845
+ /** Operator/diagnostic view: rows per tier, last runs, refusals and drops since boot. */
16846
+ var DeviceHistoryStatusSchema = object({
16847
+ startedAt: number(),
16848
+ /** The store is declared, series loaded and the first reconcile has answered. */
16849
+ ready: boolean(),
16850
+ series: number().int(),
16851
+ metrics: number().int(),
16852
+ optedOutDevices: number().int(),
16853
+ tiers: array(TierStatusSchema).readonly(),
16854
+ capture: object({
16855
+ eventsSeen: number().int(),
16856
+ pointsEmitted: number().int(),
16857
+ pointsWritten: number().int(),
16858
+ pointsDropped: number().int(),
16859
+ bufferDepth: number().int(),
16860
+ trailingEmitted: number().int(),
16861
+ reconciledPoints: number().int(),
16862
+ reconcileRuns: number().int(),
16863
+ reconcileFailures: number().int(),
16864
+ lastReconcileAt: number().nullable(),
16865
+ lastFlushAt: number().nullable(),
16866
+ flushFailures: number().int(),
16867
+ skipped: record(string(), number().int())
16868
+ }),
16869
+ ingest: object({
16870
+ accepted: number().int(),
16871
+ refused: record(string(), number().int())
16872
+ }),
16873
+ rollup: object({
16874
+ last5mAt: number().nullable(),
16875
+ last1hAt: number().nullable(),
16876
+ last1dAt: number().nullable(),
16877
+ errors: number().int(),
16878
+ dirty5m: number().int(),
16879
+ dirty1h: number().int(),
16880
+ dirty1d: number().int()
16881
+ }),
16882
+ retention: object({
16883
+ lastRunAt: number().nullable(),
16884
+ deleted: record(string(), number().int()),
16885
+ purgedSeries: number().int()
16886
+ }),
16887
+ directory: object({
16888
+ devices: number().int(),
16889
+ lastRefreshAt: number().nullable()
16890
+ })
16891
+ });
16892
+ method(object({ deviceId: number().int() }), array(SeriesInfoSchema).readonly()), method(object({ nodeId: string().min(1) }), array(SeriesInfoSchema).readonly(), { auth: "admin" }), method(object({
16893
+ deviceId: number().int(),
16894
+ ...QueryWindowShape
16895
+ }), QueryResultSchema), method(object({
16896
+ nodeId: string().min(1),
16897
+ ...QueryWindowShape
16898
+ }), QueryResultSchema, { auth: "admin" }), method(object({
16899
+ name: string().regex(/^[a-z0-9-]+$/),
16900
+ subjectKind: _enum(["device", "node"]),
16901
+ kind: DeviceHistoryKindSchema,
16902
+ unit: string().optional(),
16903
+ displayName: string().min(1),
16904
+ ingest: _enum(["points", "buckets"])
16905
+ }), object({ key: string() }), {
16906
+ kind: "mutation",
16907
+ auth: "admin"
16908
+ }), method(object({ points: array(object({
16909
+ key: string(),
16910
+ subject: DeviceHistorySubjectSchema,
16911
+ scope: string().default(""),
16912
+ ts: number(),
16913
+ value: number()
16914
+ })).max(5e3) }), IngestResultSchema, {
16915
+ kind: "mutation",
16916
+ auth: "admin"
16917
+ }), method(object({ buckets: array(object({
16918
+ key: string(),
16919
+ subject: DeviceHistorySubjectSchema,
16920
+ scope: string().default(""),
16921
+ bucketStart: number(),
16922
+ samples: number().int().positive(),
16923
+ sum: number(),
16924
+ min: number(),
16925
+ max: number(),
16926
+ last: number().nullable(),
16927
+ lastTs: number().nullable()
16928
+ })).max(5e3) }), IngestResultSchema, {
16929
+ kind: "mutation",
16930
+ auth: "admin"
16931
+ }), method(object({ deviceId: number().int() }), object({ enabled: boolean() })), method(object({
16932
+ deviceId: number().int(),
16933
+ enabled: boolean()
16934
+ }), object({ enabled: boolean() }), {
16935
+ kind: "mutation",
16936
+ auth: "admin"
16937
+ }), method(object({}), DeviceHistoryStatusSchema, { auth: "admin" });
16938
+ var DeviceCaStatusSchema = discriminatedUnion("state", [
16939
+ object({ state: literal("absent") }),
16940
+ object({
16941
+ state: literal("ready"),
16942
+ subject: string(),
16943
+ fingerprintSha256: string(),
16944
+ validTo: string()
16945
+ }),
16946
+ object({
16947
+ state: literal("unavailable"),
16948
+ reason: string()
16949
+ })
16950
+ ]);
16951
+ var DeviceCertificateSettingsSchema = object({ selfEnrollment: _enum(["allowed", "disabled"]) });
16952
+ method(object({
16953
+ userId: string().min(1).optional(),
16954
+ includeRevoked: boolean().default(false)
16955
+ }), array(DeviceCredentialSchema).readonly(), { auth: "admin" }), method(object({
16956
+ userId: string().min(1),
16957
+ deviceName: string().min(1).max(80)
16958
+ }), StartEnrollmentOutputSchema, {
16959
+ kind: "mutation",
16960
+ auth: "admin",
16961
+ caller: "required"
16962
+ }), method(object({ id: string().min(1) }), object({
16963
+ revoked: boolean(),
16964
+ sessionsEnded: number().int(),
16965
+ status: DeviceCredentialStatusSchema
16966
+ }), {
16967
+ kind: "mutation",
16968
+ auth: "admin",
16969
+ caller: "required"
16970
+ }), method(object({}), DeviceCertificateSettingsSchema, { auth: "admin" }), method(DeviceCertificateSettingsSchema, DeviceCertificateSettingsSchema, {
16971
+ kind: "mutation",
16972
+ auth: "admin"
16973
+ }), method(object({ id: string().min(1) }), object({ result: _enum([
16974
+ "deleted",
16975
+ "live",
16976
+ "unknown"
16977
+ ]) }), {
16978
+ kind: "mutation",
16979
+ auth: "admin",
16980
+ caller: "required"
16981
+ }), method(object({}), DeviceCaStatusSchema, { auth: "admin" }), method(object({
16982
+ id: string().min(1),
16983
+ deviceName: string().min(1).max(80)
16984
+ }), object({ renamed: boolean() }), {
16985
+ kind: "mutation",
16986
+ auth: "admin"
16987
+ });
16988
+ /**
16989
+ * `node-certificates` - what a node holds and the two renewals the hub drives
16990
+ * (D856): the node's MESH leaf (mutual TLS to the hub) and its HTTPS leaf (issued
16991
+ * from the hub's local CA so a device that trusts the hub root trusts the node).
16992
+ *
16993
+ * Provided by the AGENT RUNTIME (the synthetic `agent-runtime` addon id, like
16994
+ * `server-management`): the hub reaches it through the ordinary node-pinned
16995
+ * capability call - no new Moleculer action. The hub's own certificates are
16996
+ * handled in-process by the hub (`certificates` router), so this cap has no hub
16997
+ * provider.
16998
+ *
16999
+ * No private key ever crosses the mesh: the node generates its key pair, sends a
17000
+ * CSR, and the hub returns only the signed certificate.
17001
+ */
17002
+ var MeshLeafInfoSchema = object({
17003
+ cn: string(),
17004
+ serial: string(),
17005
+ fingerprintSha256: string(),
17006
+ notBefore: number(),
17007
+ notAfter: number()
17008
+ });
17009
+ var HttpsLeafInfoSchema = object({
17010
+ fingerprintSha256: string(),
17011
+ issuer: string(),
17012
+ sans: array(string()),
17013
+ notBefore: number(),
17014
+ notAfter: number()
17015
+ });
17016
+ var NodeHttpsStateSchema = discriminatedUnion("state", [object({
17017
+ state: literal("served"),
17018
+ leaf: HttpsLeafInfoSchema
17019
+ }), object({
17020
+ state: literal("not-issued"),
17021
+ reason: string()
17022
+ })]);
17023
+ method(object({}), object({
17024
+ nodeId: string(),
17025
+ mesh: MeshLeafInfoSchema.nullable(),
17026
+ https: NodeHttpsStateSchema
17027
+ }), { auth: "admin" }), method(object({}), object({ csrPem: string() }), {
17028
+ kind: "mutation",
17029
+ auth: "admin"
17030
+ }), method(object({
17031
+ certPem: string().min(1),
17032
+ caCertPem: string().min(1),
17033
+ confirmWithinMs: number().int().min(1e4).max(6e5)
17034
+ }), object({
17035
+ fingerprintSha256: string(),
17036
+ serial: string()
17037
+ }), {
17038
+ kind: "mutation",
17039
+ auth: "admin"
17040
+ }), method(object({ fingerprintSha256: string() }), object({ confirmed: boolean() }), {
17041
+ kind: "mutation",
17042
+ auth: "admin"
17043
+ }), method(object({}), object({ rolledBack: boolean() }), {
17044
+ kind: "mutation",
17045
+ auth: "admin"
17046
+ }), method(object({}), object({
17047
+ csrPem: string(),
17048
+ hostnames: array(string()),
17049
+ addresses: array(string()),
17050
+ /** SHA-256 of the hub local CA this node pinned at its first install; `null` before one. */
17051
+ pinnedCaFingerprintSha256: string().nullable()
17052
+ }), {
17053
+ kind: "mutation",
17054
+ auth: "admin"
17055
+ }), method(object({}), object({ ok: literal(true) }), {
17056
+ kind: "mutation",
17057
+ auth: "admin"
17058
+ }), method(object({
17059
+ certPem: string().min(1),
17060
+ caCertPem: string().min(1),
17061
+ /**
17062
+ * The hub signals that its local CA was rotated (D227) and this leaf chains to the NEW
17063
+ * one. Only the hub, over the authenticated mesh, can say so; a leaf with a different CA
17064
+ * and no signal is refused.
17065
+ */
17066
+ caRotation: boolean().optional()
17067
+ }), HttpsLeafInfoSchema, {
17068
+ kind: "mutation",
17069
+ auth: "admin"
17070
+ });
16522
17071
  var KeychainKeySourceSchema = object({
16523
17072
  kind: _enum([
16524
17073
  "config-file",
@@ -17106,6 +17655,10 @@ var deviceDiscoveryCapability = {
17106
17655
  * `scripts/check-runtime-state-durability.ts`.
17107
17656
  */
17108
17657
  durability: "session",
17658
+ /**
17659
+ * History (D850): none — inventory.
17660
+ */
17661
+ history: "none",
17109
17662
  methods: {
17110
17663
  /**
17111
17664
  * Snapshot of the current `discovered` list. Returns the
@@ -37887,6 +38440,38 @@ var DeviceRegisterPayloadSchema = object({
37887
38440
  * omits it and the keyword backstop applies. */
37888
38441
  secretConfigPaths: array(string()).optional()
37889
38442
  });
38443
+ /** One deleted device, kept by device-manager so an addon that missed the
38444
+ * deletion can still learn of it (D865). Written by the delete path ONLY. */
38445
+ var DeviceTombstoneSchema = object({
38446
+ deviceId: number().int().positive(),
38447
+ /** Epoch ms the device was deleted. */
38448
+ deletedAt: number(),
38449
+ /** The addon that owned the device when it was deleted. */
38450
+ providerAddonId: string()
38451
+ });
38452
+ /** Data held for one device that device-manager no longer knows. */
38453
+ var OrphanDeviceDataSchema = object({
38454
+ deviceId: number().int().positive(),
38455
+ /** Rows/files per store (`<addonId>:<store>`). */
38456
+ stores: record(string(), number().int().nonnegative())
38457
+ });
38458
+ var CleanupDeletedDevicesResultSchema = object({
38459
+ /**
38460
+ * `skipped` — the absence check could not be trusted (a device-list read
38461
+ * failed or the two reads disagreed); nothing was listed or deleted.
38462
+ * `dry-run` — `devices` is what a confirmed run would delete.
38463
+ * `cleaned` — `devices` is what was deleted.
38464
+ */
38465
+ status: _enum([
38466
+ "skipped",
38467
+ "dry-run",
38468
+ "cleaned"
38469
+ ]),
38470
+ reason: string().nullable(),
38471
+ devices: array(OrphanDeviceDataSchema),
38472
+ /** Implementers that could not be asked (their stores are not in `devices`). */
38473
+ unreachable: array(string())
38474
+ });
37890
38475
  var DeviceRemovePayloadSchema = object({ deviceId: number() });
37891
38476
  var DevicePersistConfigPayloadSchema = object({
37892
38477
  deviceId: number(),
@@ -38125,6 +38710,9 @@ method(object({
38125
38710
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
38126
38711
  kind: "mutation",
38127
38712
  auth: "admin"
38713
+ }), method(object({ sinceMs: number() }), array(DeviceTombstoneSchema).readonly(), { auth: "admin" }), method(object({ confirm: boolean() }), CleanupDeletedDevicesResultSchema, {
38714
+ kind: "mutation",
38715
+ auth: "admin"
38128
38716
  }), method(object({ integrationId: string() }), object({ removed: number() }), {
38129
38717
  kind: "mutation",
38130
38718
  auth: "admin"
@@ -38587,6 +39175,72 @@ method(object({
38587
39175
  auth: "admin"
38588
39176
  });
38589
39177
  /**
39178
+ * `device-removal-hook` — the reliable way an addon learns a device was DELETED,
39179
+ * so it can delete what it keeps for that device (D865).
39180
+ *
39181
+ * ## Why an event was not enough
39182
+ *
39183
+ * `DeviceUnregistered` is telemetry and may be dropped (D8, D11). An addon that
39184
+ * hung its per-device clean-up on it left durable garbage whenever the event was
39185
+ * lost, the addon was down, or it was restarting at that moment — footage,
39186
+ * analytics rows and settings of device 4483 outlived the device. Loss of this
39187
+ * signal corrupts durable state, so it travels by RPC with a recorded fact
39188
+ * behind it (D8: "if loss would corrupt durable state, use RPC plus a reconcile").
39189
+ *
39190
+ * ## The three pieces
39191
+ *
39192
+ * 1. device-manager records a TOMBSTONE per deleted device
39193
+ * (`deviceManager.listDeletedSince`). The tombstone is written by the delete
39194
+ * path only — a device that is unregistered, offline or whose provider is
39195
+ * restarting is NOT deleted and gets none.
39196
+ * 2. After a delete, device-manager calls `onDeviceRemoved` on EVERY provider of
39197
+ * this collection, one by one, with bounded retries, logged.
39198
+ * 3. An implementer that was down asks `deviceManager.listDeletedSince` at start
39199
+ * (and periodically) and runs its own `onDeviceRemoved` for each tombstone it
39200
+ * may have missed. Reconcile is driven by tombstones, never by "the device is
39201
+ * not in the current list": a failed or partial list read must not destroy data.
39202
+ *
39203
+ * ## Contract for an implementer
39204
+ *
39205
+ * - **Idempotent.** It is called again by a retry, by a reconcile and by the
39206
+ * admin cleanup of data that predates tombstones. A device with nothing left
39207
+ * answers zero counts.
39208
+ * - **One key per store** in `counts`, deleted rows/files per store, so the log
39209
+ * line and the admin dry run can say where the data was.
39210
+ * - **Training datasets are NOT device data** and are never deleted by a hook.
39211
+ * - **Per-device SETTINGS are not hooked.** Configuration lives in the standard
39212
+ * per-device store (`ctx.settings.*DeviceStore`), which device-manager clears
39213
+ * for every addon centrally at delete time. This hook is for device DATA
39214
+ * that is not settings: footage, event media, analytics rows, history.
39215
+ * - A provider that cannot tell is allowed to throw; the caller retries it.
39216
+ *
39217
+ * `listDeviceData` is the read side of the admin "Clean data of deleted devices"
39218
+ * action: which device ids this addon holds data for, and how much per store. It
39219
+ * never deletes. device-manager decides which of those ids are absent from the
39220
+ * fleet, from TWO consistent reads of the device list (D49).
39221
+ */
39222
+ /** What a hook is told about the deletion. */
39223
+ var DeviceRemovedInputSchema = object({
39224
+ deviceId: number().int().positive(),
39225
+ /** Epoch ms the device was deleted (the tombstone's `deletedAt`). */
39226
+ deletedAt: number(),
39227
+ /** The addon that owned the device, as device-manager last knew it. */
39228
+ providerAddonId: string()
39229
+ });
39230
+ /** Rows/files/refs deleted, per store this implementer owns. */
39231
+ var DeviceRemovedResultSchema = object({ counts: record(string(), number().int().nonnegative()) });
39232
+ /** One (store, device) pair an implementer holds data for. */
39233
+ var DeviceDataEntrySchema = object({
39234
+ store: string(),
39235
+ deviceId: number().int().positive(),
39236
+ /** Rows (or files) held for the device in the store. */
39237
+ count: number().int().nonnegative()
39238
+ });
39239
+ method(DeviceRemovedInputSchema, DeviceRemovedResultSchema, {
39240
+ kind: "mutation",
39241
+ auth: "admin"
39242
+ }), method(_void(), array(DeviceDataEntrySchema).readonly(), { auth: "admin" });
39243
+ /**
38590
39244
  * `failure-contribution` — the capability an addon reports its OWN losses
38591
39245
  * through, per camera, with the denominator attached. It stores nothing.
38592
39246
  *
@@ -39594,7 +40248,13 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
39594
40248
  * `beginExternalLink` offer exactly the redirect methods that set this,
39595
40249
  * whatever their id. Absent / false — a login button only.
39596
40250
  */
39597
- linkable: boolean().optional()
40251
+ linkable: boolean().optional(),
40252
+ /**
40253
+ * How a NATIVE app starts this method (D858): `{ kind: 'browser', startPath }`
40254
+ * when the route understands the app flow query (`app=1&code_challenge=…`),
40255
+ * `null`/absent when there is no app path - the app hides the method.
40256
+ */
40257
+ app: AppLoginPathSchema.nullable().optional()
39598
40258
  }),
39599
40259
  object({
39600
40260
  kind: literal("widget"),
@@ -39606,7 +40266,9 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
39606
40266
  bundle: string(),
39607
40267
  /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
39608
40268
  remote: WidgetRemoteSchema,
39609
- stage: LoginStageEnum
40269
+ stage: LoginStageEnum,
40270
+ /** Widgets are web ceremonies: no native app path (D858). Absent reads as `null`. */
40271
+ app: AppLoginPathSchema.nullable().optional()
39610
40272
  }),
39611
40273
  object({
39612
40274
  kind: literal("passkey"),
@@ -39618,7 +40280,9 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
39618
40280
  /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
39619
40281
  rpId: string(),
39620
40282
  /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
39621
- origin: string().nullable()
40283
+ origin: string().nullable(),
40284
+ /** Set by the hub for the primary arm; a second-factor passkey has no app path (D858). */
40285
+ app: AppLoginPathSchema.nullable().optional()
39622
40286
  })
39623
40287
  ]);
39624
40288
  method(_void(), array(LoginMethodContributionSchema).readonly());
@@ -40128,7 +40792,21 @@ var NetworkEndpointSchema = object({
40128
40792
  url: string(),
40129
40793
  hostname: string(),
40130
40794
  port: number(),
40131
- protocol: _enum(["http", "https"])
40795
+ protocol: _enum(["http", "https"]),
40796
+ /**
40797
+ * Whether this address survives a restart of its provider. `false` = it is
40798
+ * re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
40799
+ * hub never selects such an address AUTOMATICALLY for a role (D860), though
40800
+ * it stays visible and an operator may still flag it. Absent = stable: every
40801
+ * provider except a Quick tunnel owns a name that does not change.
40802
+ */
40803
+ stable: boolean().optional()
40804
+ });
40805
+ object({
40806
+ url: string(),
40807
+ /** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
40808
+ stable: boolean().optional(),
40809
+ updated: boolean().optional()
40132
40810
  });
40133
40811
  var NetworkAccessStatusSchema = object({
40134
40812
  connected: boolean(),
@@ -41225,6 +41903,10 @@ var alarmPanelCapability = {
41225
41903
  * `scripts/check-runtime-state-durability.ts`.
41226
41904
  */
41227
41905
  durability: "restored",
41906
+ /**
41907
+ * History (D850): none — enum state; arm/disarm history belongs in the events ledger.
41908
+ */
41909
+ history: "none",
41228
41910
  /** Clock fields: written, but excluded from the compare that decides
41229
41911
  * whether persisting is worth a SQLite commit. */
41230
41912
  volatileStateFields: ["lastChangedAt"]
@@ -47876,7 +48558,11 @@ var zonesCapability = {
47876
48558
  * See `RuntimeStateDurability`. Enforced by
47877
48559
  * `scripts/check-runtime-state-durability.ts`.
47878
48560
  */
47879
- durability: "restored"
48561
+ durability: "restored",
48562
+ /**
48563
+ * History (D850): none — configuration arrays.
48564
+ */
48565
+ history: "none"
47880
48566
  };
47881
48567
  /**
47882
48568
  * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
@@ -50333,7 +51019,22 @@ var SsoBridgeClaimsSchema = object({
50333
51019
  * request to present the matching cookie — a login bridge is useless in
50334
51020
  * any other browser.
50335
51021
  */
50336
- browserBinding: string().optional()
51022
+ browserBinding: string().optional(),
51023
+ /**
51024
+ * The native app that started this sign-in (D858): its PKCE challenge,
51025
+ * `state`, purpose and allow-listed return URL, copied from the server-side
51026
+ * pending OIDC state. A bridge carrying it is finished ONLY at
51027
+ * `/api/auth/sso/finish-app` (which mints a handoff code, never a web
51028
+ * session); `/api/auth/sso/finish` refuses it.
51029
+ */
51030
+ app: object({
51031
+ codeChallenge: string(),
51032
+ state: string(),
51033
+ purpose: _enum(["new-session", "step-up"]),
51034
+ redirect: string(),
51035
+ /** The page language the app asked for (D858); the confirm page speaks it. */
51036
+ lang: _enum(["en", "it"]).optional()
51037
+ }).optional()
50337
51038
  });
50338
51039
  method(object({
50339
51040
  claims: SsoBridgeClaimsSchema,
@@ -50720,6 +51421,10 @@ var streamSignalsCapability = {
50720
51421
  * `scripts/check-runtime-state-durability.ts`.
50721
51422
  */
50722
51423
  durability: "session",
51424
+ /**
51425
+ * History (D850): none — array state.
51426
+ */
51427
+ history: "none",
50723
51428
  methods: {
50724
51429
  /**
50725
51430
  * What this device can emit. Empty is a valid and common answer — most
@@ -52986,7 +53691,12 @@ var SearchResultSchema = object({
52986
53691
  publishedAt: string(),
52987
53692
  author: string(),
52988
53693
  installed: boolean(),
52989
- installedVersion: string().optional()
53694
+ installedVersion: string().optional(),
53695
+ /**
53696
+ * Capability names the package's addons declare (from its published
53697
+ * manifest). `null` = the registry lookup failed — unknown, never `[]` (D393).
53698
+ */
53699
+ capabilities: array(string()).readonly().nullable()
52990
53700
  });
52991
53701
  var AutoUpdateSettingsSchema = object({
52992
53702
  channel: ChannelSchema,
@@ -53148,7 +53858,11 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
53148
53858
  }), method(object({ packageName: string().optional() }), PackageInstallationsSchema, { auth: "admin" }), method(_void(), ReloadPackagesResultSchema, {
53149
53859
  kind: "mutation",
53150
53860
  auth: "admin"
53151
- }), method(object({ query: string().optional() }), array(SearchResultSchema)), method(object({ nodeId: string().optional() }), array(PackageUpdateSchema).readonly(), { auth: "admin" }), method(object({
53861
+ }), method(object({
53862
+ query: string().optional(),
53863
+ /** Only packages whose addons declare this capability (unknown ones excluded). */
53864
+ capability: string().optional()
53865
+ }), array(SearchResultSchema)), method(object({ nodeId: string().optional() }), array(PackageUpdateSchema).readonly(), { auth: "admin" }), method(object({
53152
53866
  name: string().min(1),
53153
53867
  version: string().optional(),
53154
53868
  nodeId: string().optional()
@@ -53284,6 +53998,36 @@ var airQualitySensorCapability = {
53284
53998
  * `scripts/check-runtime-state-durability.ts`.
53285
53999
  */
53286
54000
  durability: "restored",
54001
+ /**
54002
+ * History (D850): recorded series.
54003
+ */
54004
+ history: { fields: {
54005
+ co2Ppm: {
54006
+ kind: "gauge",
54007
+ unit: "ppm",
54008
+ deadband: 5
54009
+ },
54010
+ vocPpb: {
54011
+ kind: "gauge",
54012
+ unit: "ppb",
54013
+ deadband: 5
54014
+ },
54015
+ pm25: {
54016
+ kind: "gauge",
54017
+ unit: "ug/m3",
54018
+ deadband: 1
54019
+ },
54020
+ pm10: {
54021
+ kind: "gauge",
54022
+ unit: "ug/m3",
54023
+ deadband: 1
54024
+ },
54025
+ aqi: {
54026
+ kind: "gauge",
54027
+ unit: "count",
54028
+ deadband: 1
54029
+ }
54030
+ } },
53287
54031
  /** Clock fields: written, but excluded from the compare that decides
53288
54032
  * whether persisting is worth a SQLite commit. */
53289
54033
  volatileStateFields: ["lastFetchedAt"]
@@ -53326,6 +54070,15 @@ var ambientLightSensorCapability = {
53326
54070
  * `scripts/check-runtime-state-durability.ts`.
53327
54071
  */
53328
54072
  durability: "restored",
54073
+ /**
54074
+ * History (D850): recorded series. Wide range, so a RELATIVE deadband (10 %).
54075
+ */
54076
+ history: { fields: { lux: {
54077
+ kind: "gauge",
54078
+ unit: "lx",
54079
+ deadbandPct: 10,
54080
+ minIntervalMs: 6e4
54081
+ } } },
53329
54082
  /** Clock fields: written, but excluded from the compare that decides
53330
54083
  * whether persisting is worth a SQLite commit. */
53331
54084
  volatileStateFields: ["lastFetchedAt"]
@@ -53453,7 +54206,11 @@ var audioMetricsCapability = {
53453
54206
  * See `RuntimeStateDurability`. Enforced by
53454
54207
  * `scripts/check-runtime-state-durability.ts`.
53455
54208
  */
53456
- durability: "session"
54209
+ durability: "session",
54210
+ /**
54211
+ * History (D850): none — the level is the addon series `pipeline-analytics/audio-level` (producer `record`); the other numbers move every inference window and duplicate it.
54212
+ */
54213
+ history: "none"
53457
54214
  };
53458
54215
  /**
53459
54216
  * Automation-control cap. Models HA `automation.*` entities on
@@ -53522,7 +54279,11 @@ var automationControlCapability = {
53522
54279
  * See `RuntimeStateDurability`. Enforced by
53523
54280
  * `scripts/check-runtime-state-durability.ts`.
53524
54281
  */
53525
- durability: "session"
54282
+ durability: "session",
54283
+ /**
54284
+ * History (D850): none — configuration and transient run state.
54285
+ */
54286
+ history: "none"
53526
54287
  };
53527
54288
  /**
53528
54289
  * Battery status snapshot. Emitted by providers whose device is
@@ -53703,6 +54464,13 @@ onStatusChanged: { data: object({
53703
54464
  * `scripts/check-runtime-state-durability.ts`.
53704
54465
  */
53705
54466
  durability: "restored",
54467
+ /**
54468
+ * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
54469
+ */
54470
+ history: { fields: { percentage: {
54471
+ kind: "gauge",
54472
+ unit: "%"
54473
+ } } },
53706
54474
  /** Clock fields: written, but excluded from the compare that decides
53707
54475
  * whether persisting is worth a SQLite commit. */
53708
54476
  volatileStateFields: ["lastUpdated"]
@@ -53742,6 +54510,10 @@ var binaryCapability = {
53742
54510
  * `scripts/check-runtime-state-durability.ts`.
53743
54511
  */
53744
54512
  durability: "restored",
54513
+ /**
54514
+ * History (D850): recorded series.
54515
+ */
54516
+ history: { fields: { on: { kind: "boolean" } } },
53745
54517
  /** Clock fields: written, but excluded from the compare that decides
53746
54518
  * whether persisting is worth a SQLite commit. */
53747
54519
  volatileStateFields: ["lastChangedAt"]
@@ -53804,7 +54576,15 @@ onBrightnessChanged: { data: object({
53804
54576
  * See `RuntimeStateDurability`. Enforced by
53805
54577
  * `scripts/check-runtime-state-durability.ts`.
53806
54578
  */
53807
- durability: "session"
54579
+ durability: "session",
54580
+ /**
54581
+ * History (D850): recorded series. Actuator tier: level keeps every change.
54582
+ */
54583
+ history: { fields: { percentage: {
54584
+ kind: "gauge",
54585
+ unit: "%",
54586
+ minIntervalMs: 0
54587
+ } } }
53808
54588
  };
53809
54589
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
53810
54590
  kind: "mutation",
@@ -54138,6 +54918,10 @@ var carbonMonoxideCapability = {
54138
54918
  * `scripts/check-runtime-state-durability.ts`.
54139
54919
  */
54140
54920
  durability: "restored",
54921
+ /**
54922
+ * History (D850): recorded series.
54923
+ */
54924
+ history: { fields: { detected: { kind: "boolean" } } },
54141
54925
  /** Clock fields: written, but excluded from the compare that decides
54142
54926
  * whether persisting is worth a SQLite commit. */
54143
54927
  volatileStateFields: ["lastChangedAt"]
@@ -54307,7 +55091,37 @@ var climateControlCapability = {
54307
55091
  * See `RuntimeStateDurability`. Enforced by
54308
55092
  * `scripts/check-runtime-state-durability.ts`.
54309
55093
  */
54310
- durability: "session"
55094
+ durability: "session",
55095
+ /**
55096
+ * History (D850): recorded series. Celsius by contract; setpoints keep every change (`minIntervalMs: 0`).
55097
+ */
55098
+ history: { fields: {
55099
+ currentTemp: {
55100
+ kind: "gauge",
55101
+ unit: "°C",
55102
+ deadband: .1
55103
+ },
55104
+ currentHumidity: {
55105
+ kind: "gauge",
55106
+ unit: "%",
55107
+ deadband: .5
55108
+ },
55109
+ target: {
55110
+ kind: "gauge",
55111
+ unit: "°C",
55112
+ minIntervalMs: 0
55113
+ },
55114
+ targetHigh: {
55115
+ kind: "gauge",
55116
+ unit: "°C",
55117
+ minIntervalMs: 0
55118
+ },
55119
+ targetLow: {
55120
+ kind: "gauge",
55121
+ unit: "°C",
55122
+ minIntervalMs: 0
55123
+ }
55124
+ } }
54311
55125
  };
54312
55126
  /**
54313
55127
  * Color-light cap. Coexists with `switch` (on/off) and `brightness`
@@ -54424,7 +55238,11 @@ onColorChanged: { data: object({
54424
55238
  * See `RuntimeStateDurability`. Enforced by
54425
55239
  * `scripts/check-runtime-state-durability.ts`.
54426
55240
  */
54427
- durability: "session"
55241
+ durability: "session",
55242
+ /**
55243
+ * History (D850): none — nested and cosmetic.
55244
+ */
55245
+ history: "none"
54428
55246
  };
54429
55247
  var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
54430
55248
  object({
@@ -54490,6 +55308,10 @@ var connectivityCapability = {
54490
55308
  * `scripts/check-runtime-state-durability.ts`.
54491
55309
  */
54492
55310
  durability: "restored",
55311
+ /**
55312
+ * History (D850): none — duplicates device-status.online (one authority for availability).
55313
+ */
55314
+ history: "none",
54493
55315
  /** Clock fields: written, but excluded from the compare that decides
54494
55316
  * whether persisting is worth a SQLite commit. */
54495
55317
  volatileStateFields: ["lastChangedAt"]
@@ -54614,6 +55436,10 @@ reset: method(object({
54614
55436
  * `scripts/check-runtime-state-durability.ts`.
54615
55437
  */
54616
55438
  durability: "restored",
55439
+ /**
55440
+ * History (D850): none — `items` is an array keyed by runtime ids; a history path never enters it.
55441
+ */
55442
+ history: "none",
54617
55443
  /** Clocks, not content: a re-read that changed no item costs no commit. */
54618
55444
  volatileStateFields: ["lastChangedAt", "lastFetchedAt"]
54619
55445
  };
@@ -54654,6 +55480,10 @@ var contactCapability = {
54654
55480
  * `scripts/check-runtime-state-durability.ts`.
54655
55481
  */
54656
55482
  durability: "restored",
55483
+ /**
55484
+ * History (D850): recorded series.
55485
+ */
55486
+ history: { fields: { entryOpen: { kind: "boolean" } } },
54657
55487
  /** Clock fields: written, but excluded from the compare that decides
54658
55488
  * whether persisting is worth a SQLite commit. */
54659
55489
  volatileStateFields: ["lastChangedAt"]
@@ -54769,7 +55599,11 @@ var controlCapability = {
54769
55599
  * See `RuntimeStateDurability`. Enforced by
54770
55600
  * `scripts/check-runtime-state-durability.ts`.
54771
55601
  */
54772
- durability: "session"
55602
+ durability: "session",
55603
+ /**
55604
+ * History (D850): none — `value` is a number/string union; configuration entity.
55605
+ */
55606
+ history: "none"
54773
55607
  };
54774
55608
  var CoverStatusSchema = object({
54775
55609
  /** Lifecycle state of the cover. */
@@ -54838,6 +55672,14 @@ var coverCapability = {
54838
55672
  * `scripts/check-runtime-state-durability.ts`.
54839
55673
  */
54840
55674
  durability: "restored",
55675
+ /**
55676
+ * History (D850): recorded series. Actuator tier: position keeps every tick of a movement.
55677
+ */
55678
+ history: { fields: { position: {
55679
+ kind: "gauge",
55680
+ unit: "%",
55681
+ minIntervalMs: 0
55682
+ } } },
54841
55683
  /** Clock fields: written, but excluded from the compare that decides
54842
55684
  * whether persisting is worth a SQLite commit. */
54843
55685
  volatileStateFields: ["lastChangedAt"]
@@ -54942,6 +55784,10 @@ var dayNightCapability = {
54942
55784
  * `scripts/check-runtime-state-durability.ts`.
54943
55785
  */
54944
55786
  durability: "restored",
55787
+ /**
55788
+ * History (D850): none — firmware configuration, not a measurement.
55789
+ */
55790
+ history: "none",
54945
55791
  /** Clock fields: written, but excluded from the compare that decides
54946
55792
  * whether persisting is worth a SQLite commit. */
54947
55793
  volatileStateFields: ["lastFetchedAt"]
@@ -54999,6 +55845,10 @@ onStatusChanged: { data: object({
54999
55845
  * `scripts/check-runtime-state-durability.ts`.
55000
55846
  */
55001
55847
  durability: "restored",
55848
+ /**
55849
+ * History (D850): recorded series. The ONE availability series.
55850
+ */
55851
+ history: { fields: { online: { kind: "boolean" } } },
55002
55852
  /** Clock fields: written, but excluded from the compare that decides
55003
55853
  * whether persisting is worth a SQLite commit. */
55004
55854
  volatileStateFields: ["lastChangedAt"]
@@ -55065,7 +55915,11 @@ onPressed: {
55065
55915
  * See `RuntimeStateDurability`. Enforced by
55066
55916
  * `scripts/check-runtime-state-durability.ts`.
55067
55917
  */
55068
- durability: "restored"
55918
+ durability: "restored",
55919
+ /**
55920
+ * History (D850): none — `pressCountSinceStart` resets with the addon, so it is not a monotonic meter; presses live in the events ledger.
55921
+ */
55922
+ history: "none"
55069
55923
  };
55070
55924
  /**
55071
55925
  * Enum-state sensor — a string value picked from a finite option set.
@@ -55113,6 +55967,10 @@ var enumSensorCapability = {
55113
55967
  * `scripts/check-runtime-state-durability.ts`.
55114
55968
  */
55115
55969
  durability: "restored",
55970
+ /**
55971
+ * History (D850): none — text/enum state has no history (non-goal).
55972
+ */
55973
+ history: "none",
55116
55974
  /** Clock fields: written, but excluded from the compare that decides
55117
55975
  * whether persisting is worth a SQLite commit. */
55118
55976
  volatileStateFields: ["lastFetchedAt"]
@@ -55158,7 +56016,11 @@ var eventEmitterCapability = {
55158
56016
  * See `RuntimeStateDurability`. Enforced by
55159
56017
  * `scripts/check-runtime-state-durability.ts`.
55160
56018
  */
55161
- durability: "session"
56019
+ durability: "session",
56020
+ /**
56021
+ * History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
56022
+ */
56023
+ history: "none"
55162
56024
  };
55163
56025
  var IdentitySchema = object({
55164
56026
  id: string(),
@@ -55615,7 +56477,18 @@ var fanControlCapability = {
55615
56477
  * See `RuntimeStateDurability`. Enforced by
55616
56478
  * `scripts/check-runtime-state-durability.ts`.
55617
56479
  */
55618
- durability: "session"
56480
+ durability: "session",
56481
+ /**
56482
+ * History (D850): recorded series. Actuator tier: speed keeps every change.
56483
+ */
56484
+ history: { fields: {
56485
+ percentage: {
56486
+ kind: "gauge",
56487
+ unit: "%",
56488
+ minIntervalMs: 0
56489
+ },
56490
+ oscillating: { kind: "boolean" }
56491
+ } }
55619
56492
  };
55620
56493
  /**
55621
56494
  * Per-device feature/identity probe slice. Holds the runtime-resolved
@@ -55698,7 +56571,11 @@ onProbeChanged: { data: object({
55698
56571
  * See `RuntimeStateDurability`. Enforced by
55699
56572
  * `scripts/check-runtime-state-durability.ts`.
55700
56573
  */
55701
- durability: "session"
56574
+ durability: "session",
56575
+ /**
56576
+ * History (D850): none — static identity and capability probe.
56577
+ */
56578
+ history: "none"
55702
56579
  };
55703
56580
  /**
55704
56581
  * Water leak / moisture sensor. Boolean "is liquid currently
@@ -55733,6 +56610,10 @@ var floodCapability = {
55733
56610
  * `scripts/check-runtime-state-durability.ts`.
55734
56611
  */
55735
56612
  durability: "restored",
56613
+ /**
56614
+ * History (D850): recorded series.
56615
+ */
56616
+ history: { fields: { flooded: { kind: "boolean" } } },
55736
56617
  /** Clock fields: written, but excluded from the compare that decides
55737
56618
  * whether persisting is worth a SQLite commit. */
55738
56619
  volatileStateFields: ["lastChangedAt"]
@@ -55766,6 +56647,10 @@ var gasCapability = {
55766
56647
  * `scripts/check-runtime-state-durability.ts`.
55767
56648
  */
55768
56649
  durability: "restored",
56650
+ /**
56651
+ * History (D850): recorded series.
56652
+ */
56653
+ history: { fields: { detected: { kind: "boolean" } } },
55769
56654
  /** Clock fields: written, but excluded from the compare that decides
55770
56655
  * whether persisting is worth a SQLite commit. */
55771
56656
  volatileStateFields: ["lastChangedAt"]
@@ -55851,7 +56736,18 @@ var humidifierCapability = {
55851
56736
  * See `RuntimeStateDurability`. Enforced by
55852
56737
  * `scripts/check-runtime-state-durability.ts`.
55853
56738
  */
55854
- durability: "session"
56739
+ durability: "session",
56740
+ /**
56741
+ * History (D850): recorded series.
56742
+ */
56743
+ history: { fields: {
56744
+ currentHumidity: {
56745
+ kind: "gauge",
56746
+ unit: "%",
56747
+ deadband: .5
56748
+ },
56749
+ on: { kind: "boolean" }
56750
+ } }
55855
56751
  };
55856
56752
  /**
55857
56753
  * Single-metric humidity reading. Drives Home Assistant `sensor`
@@ -55895,6 +56791,14 @@ var humiditySensorCapability = {
55895
56791
  * `scripts/check-runtime-state-durability.ts`.
55896
56792
  */
55897
56793
  durability: "restored",
56794
+ /**
56795
+ * History (D850): recorded series.
56796
+ */
56797
+ history: { fields: { percent: {
56798
+ kind: "gauge",
56799
+ unit: "%",
56800
+ deadband: .5
56801
+ } } },
55898
56802
  /** Clock fields: written, but excluded from the compare that decides
55899
56803
  * whether persisting is worth a SQLite commit. */
55900
56804
  volatileStateFields: ["lastFetchedAt"]
@@ -55942,7 +56846,11 @@ var imageCapability = {
55942
56846
  * See `RuntimeStateDurability`. Enforced by
55943
56847
  * `scripts/check-runtime-state-durability.ts`.
55944
56848
  */
55945
- durability: "session"
56849
+ durability: "session",
56850
+ /**
56851
+ * History (D850): none — not numeric.
56852
+ */
56853
+ history: "none"
55946
56854
  };
55947
56855
  /**
55948
56856
  * Vendor-neutral image / picture-adjustment cap — the per-camera config
@@ -56099,6 +57007,10 @@ var imageSettingsCapability = {
56099
57007
  * `scripts/check-runtime-state-durability.ts`.
56100
57008
  */
56101
57009
  durability: "restored",
57010
+ /**
57011
+ * History (D850): none — camera image configuration (operator intent), not a trend.
57012
+ */
57013
+ history: "none",
56102
57014
  /** Clock fields: written, but excluded from the compare that decides
56103
57015
  * whether persisting is worth a SQLite commit. */
56104
57016
  volatileStateFields: ["lastFetchedAt"]
@@ -56484,7 +57396,11 @@ var intercomCapability = {
56484
57396
  * See `RuntimeStateDurability`. Enforced by
56485
57397
  * `scripts/check-runtime-state-durability.ts`.
56486
57398
  */
56487
- durability: "session"
57399
+ durability: "session",
57400
+ /**
57401
+ * History (D850): none — talk sessions are a ledger concern, not a trend.
57402
+ */
57403
+ history: "none"
56488
57404
  };
56489
57405
  /**
56490
57406
  * Robotic lawn-mower cap. Models HA `lawn_mower.*` entities — anything
@@ -56565,7 +57481,15 @@ var lawnMowerControlCapability = {
56565
57481
  * See `RuntimeStateDurability`. Enforced by
56566
57482
  * `scripts/check-runtime-state-durability.ts`.
56567
57483
  */
56568
- durability: "session"
57484
+ durability: "session",
57485
+ /**
57486
+ * History (D850): recorded series.
57487
+ */
57488
+ history: { fields: { batteryLevel: {
57489
+ kind: "gauge",
57490
+ unit: "%",
57491
+ deadband: 2
57492
+ } } }
56569
57493
  };
56570
57494
  /**
56571
57495
  * local-network — hub-only singleton.
@@ -56692,7 +57616,13 @@ var ConnectionEndpointSchema = object({
56692
57616
  * calls so the client can sort even if the cap returns a new
56693
57617
  * ordering between polls.
56694
57618
  */
56695
- priority: number()
57619
+ priority: number(),
57620
+ /**
57621
+ * `false` = the address changes on its own (a Quick tunnel, re-issued on
57622
+ * every respawn). Automatic role resolution skips it (D860); an explicit
57623
+ * operator flag still honours it. Absent = stable.
57624
+ */
57625
+ stable: boolean().optional()
56696
57626
  });
56697
57627
  /**
56698
57628
  * Where the advertised local port came from. Ordered most → least
@@ -56972,6 +57902,10 @@ var lockControlCapability = {
56972
57902
  * `scripts/check-runtime-state-durability.ts`.
56973
57903
  */
56974
57904
  durability: "restored",
57905
+ /**
57906
+ * History (D850): none — enum state; lock events belong in the events ledger.
57907
+ */
57908
+ history: "none",
56975
57909
  /** Clock fields: written, but excluded from the compare that decides
56976
57910
  * whether persisting is worth a SQLite commit. */
56977
57911
  volatileStateFields: ["lastChangedAt"]
@@ -57143,7 +58077,11 @@ var mediaPlayerCapability = {
57143
58077
  * See `RuntimeStateDurability`. Enforced by
57144
58078
  * `scripts/check-runtime-state-durability.ts`.
57145
58079
  */
57146
- durability: "session"
58080
+ durability: "session",
58081
+ /**
58082
+ * History (D850): none — `positionMs` moves continuously while playing; volume is transient.
58083
+ */
58084
+ history: "none"
57147
58085
  };
57148
58086
  /**
57149
58087
  * mesh-network — collection cap for mesh-VPN providers.
@@ -57452,7 +58390,14 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
57452
58390
  * See `RuntimeStateDurability`. Enforced by
57453
58391
  * `scripts/check-runtime-state-durability.ts`.
57454
58392
  */
57455
- durability: "session"
58393
+ durability: "session",
58394
+ /**
58395
+ * History (D850): recorded series. A PIR sensor is a series; a camera is not (its motion is an addon series and a ledger).
58396
+ */
58397
+ history: { fields: { detected: {
58398
+ kind: "boolean",
58399
+ excludeDeviceTypes: [DeviceType.Camera]
58400
+ } } }
57456
58401
  };
57457
58402
  /**
57458
58403
  * Motion-trigger toggle for accessory devices.
@@ -57524,7 +58469,11 @@ var motionTriggerCapability = {
57524
58469
  * See `RuntimeStateDurability`. Enforced by
57525
58470
  * `scripts/check-runtime-state-durability.ts`.
57526
58471
  */
57527
- durability: "session"
58472
+ durability: "session",
58473
+ /**
58474
+ * History (D850): none — configuration switch.
58475
+ */
58476
+ history: "none"
57528
58477
  };
57529
58478
  /**
57530
58479
  * Motion-zones share the same MaskShape vocabulary as privacy-mask — the
@@ -57599,6 +58548,10 @@ var motionZonesCapability = {
57599
58548
  * `scripts/check-runtime-state-durability.ts`.
57600
58549
  */
57601
58550
  durability: "restored",
58551
+ /**
58552
+ * History (D850): none — configuration.
58553
+ */
58554
+ history: "none",
57602
58555
  /** Clock fields: written, but excluded from the compare that decides
57603
58556
  * whether persisting is worth a SQLite commit. */
57604
58557
  volatileStateFields: ["lastFetchedAt"]
@@ -57728,6 +58681,10 @@ var nativeObjectDetectionCapability = {
57728
58681
  * `scripts/check-runtime-state-durability.ts`.
57729
58682
  */
57730
58683
  durability: "restored",
58684
+ /**
58685
+ * History (D850): none — event-like and nested per class; detections live in the objects ledger.
58686
+ */
58687
+ history: "none",
57731
58688
  /** Clock fields: written, but excluded from the compare that decides
57732
58689
  * whether persisting is worth a SQLite commit. */
57733
58690
  volatileStateFields: ["lastFetchedAt"]
@@ -58011,7 +58968,11 @@ var navigationCapability = {
58011
58968
  * See `RuntimeStateDurability`. Enforced by
58012
58969
  * `scripts/check-runtime-state-durability.ts`.
58013
58970
  */
58014
- durability: "session"
58971
+ durability: "session",
58972
+ /**
58973
+ * History (D850): none — control state of a robotic camera, configuration-like.
58974
+ */
58975
+ history: "none"
58015
58976
  };
58016
58977
  /**
58017
58978
  * Network-link snapshot. Same shape for every provider (a Reolink wifi
@@ -58111,6 +59072,23 @@ onStatusChanged: { data: object({
58111
59072
  * `scripts/check-runtime-state-durability.ts`.
58112
59073
  */
58113
59074
  durability: "restored",
59075
+ /**
59076
+ * History (D850): recorded series.
59077
+ */
59078
+ history: { fields: {
59079
+ signalPercent: {
59080
+ kind: "gauge",
59081
+ unit: "%",
59082
+ deadband: 3,
59083
+ minIntervalMs: 6e4
59084
+ },
59085
+ rssiDbm: {
59086
+ kind: "gauge",
59087
+ unit: "dBm",
59088
+ deadband: 2,
59089
+ minIntervalMs: 6e4
59090
+ }
59091
+ } },
58114
59092
  /** Clock fields: written, but excluded from the compare that decides
58115
59093
  * whether persisting is worth a SQLite commit. */
58116
59094
  volatileStateFields: ["lastUpdated"]
@@ -58499,7 +59477,11 @@ onSent: { data: object({
58499
59477
  * See `RuntimeStateDurability`. Enforced by
58500
59478
  * `scripts/check-runtime-state-durability.ts`.
58501
59479
  */
58502
- durability: "session"
59480
+ durability: "session",
59481
+ /**
59482
+ * History (D850): none — `queueDepth` is outbox operational state, not a device trend.
59483
+ */
59484
+ history: "none"
58503
59485
  };
58504
59486
  /**
58505
59487
  * Generic numeric sensor — last-resort fallback when no typed numeric
@@ -58550,6 +59532,14 @@ var numericSensorCapability = {
58550
59532
  * `scripts/check-runtime-state-durability.ts`.
58551
59533
  */
58552
59534
  durability: "restored",
59535
+ /**
59536
+ * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
59537
+ */
59538
+ history: { fields: { value: {
59539
+ kind: "gauge",
59540
+ unitFrom: "unit",
59541
+ minIntervalMs: 3e4
59542
+ } } },
58553
59543
  /** Clock fields: written, but excluded from the compare that decides
58554
59544
  * whether persisting is worth a SQLite commit. */
58555
59545
  volatileStateFields: ["lastFetchedAt"]
@@ -59026,7 +60016,24 @@ var petFeederCapability = {
59026
60016
  * See `RuntimeStateDurability`. Enforced by
59027
60017
  * `scripts/check-runtime-state-durability.ts`.
59028
60018
  */
59029
- durability: "session"
60019
+ durability: "session",
60020
+ /**
60021
+ * History (D850): recorded series.
60022
+ */
60023
+ history: { fields: {
60024
+ foodLevel: { kind: "gauge" },
60025
+ food1: { kind: "gauge" },
60026
+ food2: { kind: "gauge" },
60027
+ batteryPower: {
60028
+ kind: "gauge",
60029
+ unit: "%",
60030
+ deadband: 2
60031
+ },
60032
+ desiccantLeftDays: {
60033
+ kind: "gauge",
60034
+ unit: "days"
60035
+ }
60036
+ } }
59030
60037
  };
59031
60038
  var VehicleSchema = object({
59032
60039
  id: string(),
@@ -59404,6 +60411,47 @@ var powerMeterCapability = {
59404
60411
  * `scripts/check-runtime-state-durability.ts`.
59405
60412
  */
59406
60413
  durability: "restored",
60414
+ /**
60415
+ * History (D850): recorded series. Gauges are floored at 30 s; `kwhTotal` is the one monotonic meter (counter).
60416
+ */
60417
+ history: { fields: {
60418
+ watts: {
60419
+ kind: "gauge",
60420
+ unit: "W",
60421
+ deadband: 1,
60422
+ minIntervalMs: 3e4
60423
+ },
60424
+ kwhTotal: {
60425
+ kind: "counter",
60426
+ unit: "kWh",
60427
+ deadband: .01,
60428
+ minIntervalMs: 6e4
60429
+ },
60430
+ volts: {
60431
+ kind: "gauge",
60432
+ unit: "V",
60433
+ deadband: 1,
60434
+ minIntervalMs: 3e4
60435
+ },
60436
+ amps: {
60437
+ kind: "gauge",
60438
+ unit: "A",
60439
+ deadband: .05,
60440
+ minIntervalMs: 3e4
60441
+ },
60442
+ va: {
60443
+ kind: "gauge",
60444
+ unit: "VA",
60445
+ deadband: 1,
60446
+ minIntervalMs: 3e4
60447
+ },
60448
+ powerFactor: {
60449
+ kind: "gauge",
60450
+ unit: "ratio",
60451
+ deadband: .01,
60452
+ minIntervalMs: 6e4
60453
+ }
60454
+ } },
59407
60455
  /** Clock fields: written, but excluded from the compare that decides
59408
60456
  * whether persisting is worth a SQLite commit. */
59409
60457
  volatileStateFields: ["lastFetchedAt"]
@@ -59471,6 +60519,14 @@ var presenceCapability = {
59471
60519
  * `scripts/check-runtime-state-durability.ts`.
59472
60520
  */
59473
60521
  durability: "restored",
60522
+ /**
60523
+ * History (D850): recorded series.
60524
+ */
60525
+ history: { fields: { batteryPercent: {
60526
+ kind: "gauge",
60527
+ unit: "%",
60528
+ deadband: 1
60529
+ } } },
59474
60530
  /** Clock fields: written, but excluded from the compare that decides
59475
60531
  * whether persisting is worth a SQLite commit. */
59476
60532
  volatileStateFields: ["lastChangedAt"]
@@ -59517,6 +60573,14 @@ var pressureSensorCapability = {
59517
60573
  * `scripts/check-runtime-state-durability.ts`.
59518
60574
  */
59519
60575
  durability: "restored",
60576
+ /**
60577
+ * History (D850): recorded series.
60578
+ */
60579
+ history: { fields: { hpa: {
60580
+ kind: "gauge",
60581
+ unit: "hPa",
60582
+ deadband: .1
60583
+ } } },
59520
60584
  /** Clock fields: written, but excluded from the compare that decides
59521
60585
  * whether persisting is worth a SQLite commit. */
59522
60586
  volatileStateFields: ["lastFetchedAt"]
@@ -59676,6 +60740,10 @@ var privacyMaskCapability = {
59676
60740
  * `scripts/check-runtime-state-durability.ts`.
59677
60741
  */
59678
60742
  durability: "restored",
60743
+ /**
60744
+ * History (D850): none — configuration.
60745
+ */
60746
+ history: "none",
59679
60747
  /** Clock fields: written, but excluded from the compare that decides
59680
60748
  * whether persisting is worth a SQLite commit. */
59681
60749
  volatileStateFields: ["lastFetchedAt"]
@@ -60149,7 +61217,11 @@ var ptzAutotrackCapability = {
60149
61217
  * See `RuntimeStateDurability`. Enforced by
60150
61218
  * `scripts/check-runtime-state-durability.ts`.
60151
61219
  */
60152
- durability: "session"
61220
+ durability: "session",
61221
+ /**
61222
+ * History (D850): none — configuration and capability.
61223
+ */
61224
+ history: "none"
60153
61225
  };
60154
61226
  /**
60155
61227
  * reboot — device-scoped capability for "soft" device reboots (firmware
@@ -61604,6 +62676,10 @@ var recordingOnboardCapability = {
61604
62676
  * `scripts/check-runtime-state-durability.ts`.
61605
62677
  */
61606
62678
  durability: "restored",
62679
+ /**
62680
+ * History (D850): none — SD-card configuration and window inventory; arrays.
62681
+ */
62682
+ history: "none",
61607
62683
  /** Clock fields: written, but excluded from the compare that decides
61608
62684
  * whether persisting is worth a SQLite commit. */
61609
62685
  volatileStateFields: ["lastFetchedAt"]
@@ -61842,7 +62918,11 @@ getStatus: method(object({ deviceId: number() }), RecordingSignalStatusSchema) }
61842
62918
  * See `RuntimeStateDurability`. Enforced by
61843
62919
  * `scripts/check-runtime-state-durability.ts`.
61844
62920
  */
61845
- durability: "session"
62921
+ durability: "session",
62922
+ /**
62923
+ * History (D850): none — recording intent is already in the recordings ledger.
62924
+ */
62925
+ history: "none"
61846
62926
  };
61847
62927
  /**
61848
62928
  * scene-monitor — device-scoped reference-region state cap. An operator marks
@@ -62215,7 +63295,11 @@ var sceneMonitorCapability = {
62215
63295
  * See `RuntimeStateDurability`. Enforced by
62216
63296
  * `scripts/check-runtime-state-durability.ts`.
62217
63297
  */
62218
- durability: "session"
63298
+ durability: "session",
63299
+ /**
63300
+ * History (D850): none — nested configuration.
63301
+ */
63302
+ history: "none"
62219
63303
  };
62220
63304
  /**
62221
63305
  * Per-stage gating mode applied to the zones a rule references.
@@ -62366,7 +63450,11 @@ var scriptRunnerCapability = {
62366
63450
  * See `RuntimeStateDurability`. Enforced by
62367
63451
  * `scripts/check-runtime-state-durability.ts`.
62368
63452
  */
62369
- durability: "session"
63453
+ durability: "session",
63454
+ /**
63455
+ * History (D850): none — transient; run results belong in a ledger.
63456
+ */
63457
+ history: "none"
62370
63458
  };
62371
63459
  /**
62372
63460
  * Smoke alarm sensor — boolean "is smoke currently detected" with
@@ -62401,6 +63489,10 @@ var smokeCapability = {
62401
63489
  * `scripts/check-runtime-state-durability.ts`.
62402
63490
  */
62403
63491
  durability: "restored",
63492
+ /**
63493
+ * History (D850): recorded series.
63494
+ */
63495
+ history: { fields: { detected: { kind: "boolean" } } },
62404
63496
  /** Clock fields: written, but excluded from the compare that decides
62405
63497
  * whether persisting is worth a SQLite commit. */
62406
63498
  volatileStateFields: ["lastChangedAt"]
@@ -62500,6 +63592,10 @@ var streamCatalogCapability = {
62500
63592
  * `scripts/check-runtime-state-durability.ts`.
62501
63593
  */
62502
63594
  durability: "restored",
63595
+ /**
63596
+ * History (D850): none — stream inventory.
63597
+ */
63598
+ history: "none",
62503
63599
  /** Clock field: written, but excluded from the compare that decides whether
62504
63600
  * persisting is worth a SQLite commit — the descriptors are the value. */
62505
63601
  volatileStateFields: ["lastFetchedAt"]
@@ -62649,6 +63745,10 @@ var streamParamsCapability = {
62649
63745
  * `scripts/check-runtime-state-durability.ts`.
62650
63746
  */
62651
63747
  durability: "restored",
63748
+ /**
63749
+ * History (D850): none — encoder configuration.
63750
+ */
63751
+ history: "none",
62652
63752
  /** Clock fields: written, but excluded from the compare that decides
62653
63753
  * whether persisting is worth a SQLite commit. */
62654
63754
  volatileStateFields: ["lastFetchedAt"]
@@ -62902,6 +64002,10 @@ var switchCapability = {
62902
64002
  * `scripts/check-runtime-state-durability.ts`.
62903
64003
  */
62904
64004
  durability: "restored",
64005
+ /**
64006
+ * History (D850): recorded series.
64007
+ */
64008
+ history: { fields: { on: { kind: "boolean" } } },
62905
64009
  /** Clock fields: written, but excluded from the compare that decides
62906
64010
  * whether persisting is worth a SQLite commit. */
62907
64011
  volatileStateFields: ["lastChangedAt"],
@@ -62916,6 +64020,21 @@ var switchCapability = {
62916
64020
  }
62917
64021
  }] }
62918
64022
  };
64023
+ /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
64024
+ function hasControlCharacter(value) {
64025
+ for (const char of value) {
64026
+ const code = char.codePointAt(0) ?? 0;
64027
+ if (code <= 31 || code >= 127 && code <= 159) return true;
64028
+ }
64029
+ return false;
64030
+ }
64031
+ string().transform((value) => value.trim()).pipe(string().min(1, "The server name cannot be empty").max(48, `The server name is at most 48 characters`).refine((value) => !hasControlCharacter(value), { message: "The server name cannot contain control characters" }));
64032
+ /** What every reader gets: the name to show, and whether the hub chose it itself. */
64033
+ var ServerNameInfoSchema = object({
64034
+ name: string(),
64035
+ /** `true` when nobody named the hub: `name` is the machine hostname. */
64036
+ isDefault: boolean()
64037
+ });
62919
64038
  /**
62920
64039
  * system — system-scoped singleton capability exposing hub-level
62921
64040
  * health, feature flags, network interface enumeration, and the
@@ -62945,6 +64064,12 @@ var FeatureManifestSchema = object({
62945
64064
  backup: boolean(),
62946
64065
  repl: boolean()
62947
64066
  });
64067
+ /**
64068
+ * `system.info`: the feature flags plus the hub's human name (D861). The name is
64069
+ * `{ name, isDefault }` — `isDefault: true` means nobody named the hub and `name`
64070
+ * is the machine hostname.
64071
+ */
64072
+ var SystemInfoSchema = FeatureManifestSchema.extend({ serverName: ServerNameInfoSchema });
62948
64073
  var HealthStatusSchema = object({
62949
64074
  status: literal("ok"),
62950
64075
  uptime: number()
@@ -63417,7 +64542,7 @@ var LoggingSettingsStateSchema = object({
63417
64542
  opsLogRetentionDays: number().int().min(0),
63418
64543
  persisted: boolean()
63419
64544
  });
63420
- method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
64545
+ method(_void(), SystemInfoSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
63421
64546
  kind: "mutation",
63422
64547
  auth: "admin"
63423
64548
  }), method(_void(), _void(), {
@@ -63463,6 +64588,10 @@ var tamperCapability = {
63463
64588
  * `scripts/check-runtime-state-durability.ts`.
63464
64589
  */
63465
64590
  durability: "restored",
64591
+ /**
64592
+ * History (D850): recorded series.
64593
+ */
64594
+ history: { fields: { tampered: { kind: "boolean" } } },
63466
64595
  /** Clock fields: written, but excluded from the compare that decides
63467
64596
  * whether persisting is worth a SQLite commit. */
63468
64597
  volatileStateFields: ["lastChangedAt"]
@@ -63518,6 +64647,14 @@ var temperatureSensorCapability = {
63518
64647
  * `scripts/check-runtime-state-durability.ts`.
63519
64648
  */
63520
64649
  durability: "restored",
64650
+ /**
64651
+ * History (D850): recorded series.
64652
+ */
64653
+ history: { fields: { celsius: {
64654
+ kind: "gauge",
64655
+ unit: "°C",
64656
+ deadband: .1
64657
+ } } },
63521
64658
  /** Clock fields: written, but excluded from the compare that decides
63522
64659
  * whether persisting is worth a SQLite commit. */
63523
64660
  volatileStateFields: ["lastFetchedAt"]
@@ -63596,7 +64733,11 @@ var updateCapability = {
63596
64733
  * See `RuntimeStateDurability`. Enforced by
63597
64734
  * `scripts/check-runtime-state-durability.ts`.
63598
64735
  */
63599
- durability: "session"
64736
+ durability: "session",
64737
+ /**
64738
+ * History (D850): none — text and state.
64739
+ */
64740
+ history: "none"
63600
64741
  };
63601
64742
  var UserSummarySchema = object({
63602
64743
  id: string(),
@@ -63953,7 +65094,37 @@ var vacuumControlCapability = {
63953
65094
  * See `RuntimeStateDurability`. Enforced by
63954
65095
  * `scripts/check-runtime-state-durability.ts`.
63955
65096
  */
63956
- durability: "session"
65097
+ durability: "session",
65098
+ /**
65099
+ * History (D850): recorded series. Tank levels are fixed-object paths (`cleanWater.level`, ...).
65100
+ */
65101
+ history: { fields: {
65102
+ batteryLevel: {
65103
+ kind: "gauge",
65104
+ unit: "%",
65105
+ deadband: 2
65106
+ },
65107
+ "cleanWater.level": {
65108
+ kind: "gauge",
65109
+ unit: "%",
65110
+ deadband: 2
65111
+ },
65112
+ "dirtyWater.level": {
65113
+ kind: "gauge",
65114
+ unit: "%",
65115
+ deadband: 2
65116
+ },
65117
+ "detergent.level": {
65118
+ kind: "gauge",
65119
+ unit: "%",
65120
+ deadband: 2
65121
+ },
65122
+ "dustBin.level": {
65123
+ kind: "gauge",
65124
+ unit: "%",
65125
+ deadband: 2
65126
+ }
65127
+ } }
63957
65128
  };
63958
65129
  var ValveStatusSchema = object({
63959
65130
  /** Lifecycle state of the valve. */
@@ -64012,7 +65183,15 @@ var valveCapability = {
64012
65183
  * See `RuntimeStateDurability`. Enforced by
64013
65184
  * `scripts/check-runtime-state-durability.ts`.
64014
65185
  */
64015
- durability: "session"
65186
+ durability: "session",
65187
+ /**
65188
+ * History (D850): recorded series. Actuator tier: position keeps every change.
65189
+ */
65190
+ history: { fields: { position: {
65191
+ kind: "gauge",
65192
+ unit: "%",
65193
+ minIntervalMs: 0
65194
+ } } }
64016
65195
  };
64017
65196
  /**
64018
65197
  * Vibration / shake / impact sensor. Drives Home Assistant
@@ -64042,6 +65221,10 @@ var vibrationCapability = {
64042
65221
  * `scripts/check-runtime-state-durability.ts`.
64043
65222
  */
64044
65223
  durability: "restored",
65224
+ /**
65225
+ * History (D850): recorded series.
65226
+ */
65227
+ history: { fields: { detected: { kind: "boolean" } } },
64045
65228
  /** Clock fields: written, but excluded from the compare that decides
64046
65229
  * whether persisting is worth a SQLite commit. */
64047
65230
  volatileStateFields: ["lastChangedAt"]
@@ -64125,7 +65308,22 @@ var waterHeaterCapability = {
64125
65308
  * See `RuntimeStateDurability`. Enforced by
64126
65309
  * `scripts/check-runtime-state-durability.ts`.
64127
65310
  */
64128
- durability: "session"
65311
+ durability: "session",
65312
+ /**
65313
+ * History (D850): recorded series. The schema carries no unit; providers publish Celsius. Setpoint keeps every change.
65314
+ */
65315
+ history: { fields: {
65316
+ currentTemp: {
65317
+ kind: "gauge",
65318
+ unit: "°C",
65319
+ deadband: .5
65320
+ },
65321
+ targetTemp: {
65322
+ kind: "gauge",
65323
+ unit: "°C",
65324
+ minIntervalMs: 0
65325
+ }
65326
+ } }
64129
65327
  };
64130
65328
  /**
64131
65329
  * Weather provider cap. Models HA `weather.*` entities — a read-only
@@ -64191,7 +65389,32 @@ var weatherCapability = {
64191
65389
  * See `RuntimeStateDurability`. Enforced by
64192
65390
  * `scripts/check-runtime-state-durability.ts`.
64193
65391
  */
64194
- durability: "session"
65392
+ durability: "session",
65393
+ /**
65394
+ * History (D850): recorded series. Units follow the sibling `*Unit` fields.
65395
+ */
65396
+ history: { fields: {
65397
+ temperature: {
65398
+ kind: "gauge",
65399
+ unitFrom: "temperatureUnit",
65400
+ deadband: .2
65401
+ },
65402
+ humidity: {
65403
+ kind: "gauge",
65404
+ unit: "%",
65405
+ deadband: 1
65406
+ },
65407
+ pressure: {
65408
+ kind: "gauge",
65409
+ unitFrom: "pressureUnit",
65410
+ deadband: .5
65411
+ },
65412
+ windSpeed: {
65413
+ kind: "gauge",
65414
+ unitFrom: "windSpeedUnit",
65415
+ deadband: .5
65416
+ }
65417
+ } }
64195
65418
  };
64196
65419
  /**
64197
65420
  * Per-zone occupancy aggregation produced by the analytics frame
@@ -64433,7 +65656,11 @@ var zoneAnalyticsCapability = {
64433
65656
  * See `RuntimeStateDurability`. Enforced by
64434
65657
  * `scripts/check-runtime-state-durability.ts`.
64435
65658
  */
64436
- durability: "session"
65659
+ durability: "session",
65660
+ /**
65661
+ * History (D850): none — occupancy is the addon series `pipeline-analytics/occupancy`; the rest is per-zone arrays keyed by runtime ids.
65662
+ */
65663
+ history: "none"
64437
65664
  };
64438
65665
  /**
64439
65666
  * Stages a {@link ZoneRule} can apply to. Discriminator on the rules
@@ -64518,7 +65745,11 @@ var zoneRulesCapability = {
64518
65745
  * See `RuntimeStateDurability`. Enforced by
64519
65746
  * `scripts/check-runtime-state-durability.ts`.
64520
65747
  */
64521
- durability: "restored"
65748
+ durability: "restored",
65749
+ /**
65750
+ * History (D850): none — configuration arrays.
65751
+ */
65752
+ history: "none"
64522
65753
  };
64523
65754
  /**
64524
65755
  * `training-provider` — ONE cloud that can run the CamStack trainer.
@@ -68122,6 +69353,54 @@ Object.freeze({
68122
69353
  addonId: null,
68123
69354
  access: "create"
68124
69355
  },
69356
+ "deviceCredentials.deleteDeviceCredential": {
69357
+ capName: "device-credentials",
69358
+ capScope: "system",
69359
+ addonId: null,
69360
+ access: "delete"
69361
+ },
69362
+ "deviceCredentials.getDeviceCaStatus": {
69363
+ capName: "device-credentials",
69364
+ capScope: "system",
69365
+ addonId: null,
69366
+ access: "view"
69367
+ },
69368
+ "deviceCredentials.getDeviceCertificateSettings": {
69369
+ capName: "device-credentials",
69370
+ capScope: "system",
69371
+ addonId: null,
69372
+ access: "view"
69373
+ },
69374
+ "deviceCredentials.listDeviceCredentials": {
69375
+ capName: "device-credentials",
69376
+ capScope: "system",
69377
+ addonId: null,
69378
+ access: "view"
69379
+ },
69380
+ "deviceCredentials.renameDeviceCredential": {
69381
+ capName: "device-credentials",
69382
+ capScope: "system",
69383
+ addonId: null,
69384
+ access: "create"
69385
+ },
69386
+ "deviceCredentials.revokeDeviceCredential": {
69387
+ capName: "device-credentials",
69388
+ capScope: "system",
69389
+ addonId: null,
69390
+ access: "delete"
69391
+ },
69392
+ "deviceCredentials.setDeviceCertificateSettings": {
69393
+ capName: "device-credentials",
69394
+ capScope: "system",
69395
+ addonId: null,
69396
+ access: "create"
69397
+ },
69398
+ "deviceCredentials.startEnrollment": {
69399
+ capName: "device-credentials",
69400
+ capScope: "system",
69401
+ addonId: null,
69402
+ access: "create"
69403
+ },
68125
69404
  "deviceDiscovery.adoptDevice": {
68126
69405
  capName: "device-discovery",
68127
69406
  capScope: "device",
@@ -68224,6 +69503,66 @@ Object.freeze({
68224
69503
  addonId: null,
68225
69504
  access: "create"
68226
69505
  },
69506
+ "deviceHistory.declareSeries": {
69507
+ capName: "device-history",
69508
+ capScope: "system",
69509
+ addonId: null,
69510
+ access: "create"
69511
+ },
69512
+ "deviceHistory.getDeviceEnabled": {
69513
+ capName: "device-history",
69514
+ capScope: "system",
69515
+ addonId: null,
69516
+ access: "view"
69517
+ },
69518
+ "deviceHistory.getStatus": {
69519
+ capName: "device-history",
69520
+ capScope: "system",
69521
+ addonId: null,
69522
+ access: "view"
69523
+ },
69524
+ "deviceHistory.listNodeSeries": {
69525
+ capName: "device-history",
69526
+ capScope: "system",
69527
+ addonId: null,
69528
+ access: "view"
69529
+ },
69530
+ "deviceHistory.listSeries": {
69531
+ capName: "device-history",
69532
+ capScope: "system",
69533
+ addonId: null,
69534
+ access: "view"
69535
+ },
69536
+ "deviceHistory.query": {
69537
+ capName: "device-history",
69538
+ capScope: "system",
69539
+ addonId: null,
69540
+ access: "view"
69541
+ },
69542
+ "deviceHistory.queryNode": {
69543
+ capName: "device-history",
69544
+ capScope: "system",
69545
+ addonId: null,
69546
+ access: "view"
69547
+ },
69548
+ "deviceHistory.record": {
69549
+ capName: "device-history",
69550
+ capScope: "system",
69551
+ addonId: null,
69552
+ access: "create"
69553
+ },
69554
+ "deviceHistory.recordBuckets": {
69555
+ capName: "device-history",
69556
+ capScope: "system",
69557
+ addonId: null,
69558
+ access: "create"
69559
+ },
69560
+ "deviceHistory.setDeviceEnabled": {
69561
+ capName: "device-history",
69562
+ capScope: "system",
69563
+ addonId: null,
69564
+ access: "create"
69565
+ },
68227
69566
  "deviceManager.addLocation": {
68228
69567
  capName: "device-manager",
68229
69568
  capScope: "system",
@@ -68302,6 +69641,12 @@ Object.freeze({
68302
69641
  addonId: null,
68303
69642
  access: "create"
68304
69643
  },
69644
+ "deviceManager.cleanupDeletedDevicesData": {
69645
+ capName: "device-manager",
69646
+ capScope: "system",
69647
+ addonId: null,
69648
+ access: "create"
69649
+ },
68305
69650
  "deviceManager.createDevice": {
68306
69651
  capName: "device-manager",
68307
69652
  capScope: "system",
@@ -68482,6 +69827,12 @@ Object.freeze({
68482
69827
  addonId: null,
68483
69828
  access: "view"
68484
69829
  },
69830
+ "deviceManager.listDeletedSince": {
69831
+ capName: "device-manager",
69832
+ capScope: "system",
69833
+ addonId: null,
69834
+ access: "view"
69835
+ },
68485
69836
  "deviceManager.listDeviceIdsByCaps": {
68486
69837
  capName: "device-manager",
68487
69838
  capScope: "system",
@@ -68890,6 +70241,18 @@ Object.freeze({
68890
70241
  addonId: null,
68891
70242
  access: "create"
68892
70243
  },
70244
+ "deviceRemovalHook.listDeviceData": {
70245
+ capName: "device-removal-hook",
70246
+ capScope: "system",
70247
+ addonId: null,
70248
+ access: "view"
70249
+ },
70250
+ "deviceRemovalHook.onDeviceRemoved": {
70251
+ capName: "device-removal-hook",
70252
+ capScope: "system",
70253
+ addonId: null,
70254
+ access: "create"
70255
+ },
68893
70256
  "deviceState.claimFields": {
68894
70257
  capName: "device-state",
68895
70258
  capScope: "system",
@@ -69322,6 +70685,12 @@ Object.freeze({
69322
70685
  addonId: null,
69323
70686
  access: "delete"
69324
70687
  },
70688
+ "keychain.deleteDeviceScope": {
70689
+ capName: "keychain",
70690
+ capScope: "system",
70691
+ addonId: null,
70692
+ access: "delete"
70693
+ },
69325
70694
  "keychain.getState": {
69326
70695
  capName: "keychain",
69327
70696
  capScope: "system",
@@ -70282,6 +71651,54 @@ Object.freeze({
70282
71651
  addonId: null,
70283
71652
  access: "create"
70284
71653
  },
71654
+ "nodeCertificates.confirmMeshLeaf": {
71655
+ capName: "node-certificates",
71656
+ capScope: "system",
71657
+ addonId: null,
71658
+ access: "create"
71659
+ },
71660
+ "nodeCertificates.forgetHub": {
71661
+ capName: "node-certificates",
71662
+ capScope: "system",
71663
+ addonId: null,
71664
+ access: "delete"
71665
+ },
71666
+ "nodeCertificates.getCertificates": {
71667
+ capName: "node-certificates",
71668
+ capScope: "system",
71669
+ addonId: null,
71670
+ access: "view"
71671
+ },
71672
+ "nodeCertificates.installHttpsCertificate": {
71673
+ capName: "node-certificates",
71674
+ capScope: "system",
71675
+ addonId: null,
71676
+ access: "create"
71677
+ },
71678
+ "nodeCertificates.installMeshLeaf": {
71679
+ capName: "node-certificates",
71680
+ capScope: "system",
71681
+ addonId: null,
71682
+ access: "create"
71683
+ },
71684
+ "nodeCertificates.prepareHttpsCertificate": {
71685
+ capName: "node-certificates",
71686
+ capScope: "system",
71687
+ addonId: null,
71688
+ access: "create"
71689
+ },
71690
+ "nodeCertificates.prepareMeshRenewal": {
71691
+ capName: "node-certificates",
71692
+ capScope: "system",
71693
+ addonId: null,
71694
+ access: "create"
71695
+ },
71696
+ "nodeCertificates.rollbackMeshLeaf": {
71697
+ capName: "node-certificates",
71698
+ capScope: "system",
71699
+ addonId: null,
71700
+ access: "create"
71701
+ },
70285
71702
  "nodes.clusterAddonStatus": {
70286
71703
  capName: "nodes",
70287
71704
  capScope: "system",
@@ -75125,6 +76542,26 @@ Object.freeze({
75125
76542
  form: "single",
75126
76543
  optional: false
75127
76544
  }],
76545
+ "deviceHistory.getDeviceEnabled": [{
76546
+ name: "deviceId",
76547
+ form: "single",
76548
+ optional: false
76549
+ }],
76550
+ "deviceHistory.listSeries": [{
76551
+ name: "deviceId",
76552
+ form: "single",
76553
+ optional: false
76554
+ }],
76555
+ "deviceHistory.query": [{
76556
+ name: "deviceId",
76557
+ form: "single",
76558
+ optional: false
76559
+ }],
76560
+ "deviceHistory.setDeviceEnabled": [{
76561
+ name: "deviceId",
76562
+ form: "single",
76563
+ optional: false
76564
+ }],
75128
76565
  "deviceManager.adoptionRelease": [{
75129
76566
  name: "camDeviceId",
75130
76567
  form: "single",
@@ -75418,6 +76855,11 @@ Object.freeze({
75418
76855
  form: "single",
75419
76856
  optional: false
75420
76857
  }],
76858
+ "deviceRemovalHook.onDeviceRemoved": [{
76859
+ name: "deviceId",
76860
+ form: "single",
76861
+ optional: false
76862
+ }],
75421
76863
  "deviceState.claimFields": [{
75422
76864
  name: "deviceId",
75423
76865
  form: "single",
@@ -75607,6 +77049,11 @@ Object.freeze({
75607
77049
  form: "single",
75608
77050
  optional: false
75609
77051
  }],
77052
+ "keychain.deleteDeviceScope": [{
77053
+ name: "deviceId",
77054
+ form: "single",
77055
+ optional: false
77056
+ }],
75610
77057
  "lawnMowerControl.dock": [{
75611
77058
  name: "deviceId",
75612
77059
  form: "single",
@@ -77303,6 +78750,7 @@ Object.freeze({
77303
78750
  "network-access": "ingress",
77304
78751
  "smtp-provider": "email"
77305
78752
  });
78753
+ DeviceType.Camera;
77306
78754
  var G711_SCALE_CORRECTION_DB = {
77307
78755
  PCMU: 20 * Math.log10(4),
77308
78756
  PCMA: 20 * Math.log10(8)
@@ -77954,6 +79402,8 @@ new Set([
77954
79402
  "audio-tools",
77955
79403
  "audio-silence"
77956
79404
  ]);
79405
+ AppPageLangSchema.options;
79406
+ record(string().min(1), string());
77957
79407
  /**
77958
79408
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
77959
79409
  *
@@ -185273,71 +186723,88 @@ var intercomFailureReport = new IntercomFailureReport();
185273
186723
  var SNAPSHOT_GROUPS = [
185274
186724
  {
185275
186725
  key: "imageSnapshot",
185276
- label: "Image (getVideoInput)"
186726
+ label: "Image (getVideoInput)",
186727
+ labelKey: "provider-reolink:snapshot_groups.imageSnapshot.label"
185277
186728
  },
185278
186729
  {
185279
186730
  key: "motionSnapshot",
185280
- label: "Motion (getMotionAlarm)"
186731
+ label: "Motion (getMotionAlarm)",
186732
+ labelKey: "provider-reolink:snapshot_groups.motionSnapshot.label"
185281
186733
  },
185282
186734
  {
185283
186735
  key: "aiSensitivitySnapshot",
185284
- label: "AI sensitivity (getAiDetectionFull)"
186736
+ label: "AI sensitivity (getAiDetectionFull)",
186737
+ labelKey: "provider-reolink:snapshot_groups.aiSensitivitySnapshot.label"
185285
186738
  },
185286
186739
  {
185287
186740
  key: "encSnapshot",
185288
- label: "Encoder (getEnc)"
186741
+ label: "Encoder (getEnc)",
186742
+ labelKey: "provider-reolink:snapshot_groups.encSnapshot.label"
185289
186743
  },
185290
186744
  {
185291
186745
  key: "encOptionsSnapshot",
185292
- label: "Encoder options (getEncOptions)"
186746
+ label: "Encoder options (getEncOptions)",
186747
+ labelKey: "provider-reolink:snapshot_groups.encOptionsSnapshot.label"
185293
186748
  },
185294
186749
  {
185295
186750
  key: "maskSnapshot",
185296
- label: "Privacy mask (getMask)"
186751
+ label: "Privacy mask (getMask)",
186752
+ labelKey: "provider-reolink:snapshot_groups.maskSnapshot.label"
185297
186753
  },
185298
186754
  {
185299
186755
  key: "audioNoiseSnapshot",
185300
- label: "Audio noise (getAudioNoise)"
186756
+ label: "Audio noise (getAudioNoise)",
186757
+ labelKey: "provider-reolink:snapshot_groups.audioNoiseSnapshot.label"
185301
186758
  },
185302
186759
  {
185303
186760
  key: "autoFocusSnapshot",
185304
- label: "Auto-focus (getAutoFocus)"
186761
+ label: "Auto-focus (getAutoFocus)",
186762
+ labelKey: "provider-reolink:snapshot_groups.autoFocusSnapshot.label"
185305
186763
  },
185306
186764
  {
185307
186765
  key: "netPortSnapshot",
185308
- label: "Network ports (getNetPort)"
186766
+ label: "Network ports (getNetPort)",
186767
+ labelKey: "provider-reolink:snapshot_groups.netPortSnapshot.label"
185309
186768
  },
185310
186769
  {
185311
186770
  key: "ntpSnapshot",
185312
- label: "NTP (getNtp)"
186771
+ label: "NTP (getNtp)",
186772
+ labelKey: "provider-reolink:snapshot_groups.ntpSnapshot.label"
185313
186773
  },
185314
186774
  {
185315
186775
  key: "systemGeneralSnapshot",
185316
- label: "System general (getSystemGeneral)"
186776
+ label: "System general (getSystemGeneral)",
186777
+ labelKey: "provider-reolink:snapshot_groups.systemGeneralSnapshot.label"
185317
186778
  },
185318
186779
  {
185319
186780
  key: "osdSnapshot",
185320
- label: "OSD overlay (getOsd)"
186781
+ label: "OSD overlay (getOsd)",
186782
+ labelKey: "provider-reolink:snapshot_groups.osdSnapshot.label"
185321
186783
  },
185322
186784
  {
185323
186785
  key: "ledSnapshot",
185324
- label: "LEDs (getIrLights)"
186786
+ label: "LEDs (getIrLights)",
186787
+ labelKey: "provider-reolink:snapshot_groups.ledSnapshot.label"
185325
186788
  },
185326
186789
  {
185327
186790
  key: "pirSnapshot",
185328
- label: "PIR (getPirInfo)"
186791
+ label: "PIR (getPirInfo)",
186792
+ labelKey: "provider-reolink:snapshot_groups.pirSnapshot.label"
185329
186793
  },
185330
186794
  {
185331
186795
  key: "autoRebootSnapshot",
185332
- label: "Auto reboot (getAutoReboot)"
186796
+ label: "Auto reboot (getAutoReboot)",
186797
+ labelKey: "provider-reolink:snapshot_groups.autoRebootSnapshot.label"
185333
186798
  },
185334
186799
  {
185335
186800
  key: "emailConfigSnapshot",
185336
- label: "Email/SMTP (getEmail)"
186801
+ label: "Email/SMTP (getEmail)",
186802
+ labelKey: "provider-reolink:snapshot_groups.emailConfigSnapshot.label"
185337
186803
  },
185338
186804
  {
185339
186805
  key: "capOptionsSnapshot",
185340
- label: "Cap option probes (getOptions)"
186806
+ label: "Cap option probes (getOptions)",
186807
+ labelKey: "provider-reolink:snapshot_groups.capOptionsSnapshot.label"
185341
186808
  }
185342
186809
  ];
185343
186810
  /**
@@ -185421,12 +186888,15 @@ function buildSnapshotFreshnessSection(cache, opts) {
185421
186888
  id: "snapshotFreshness",
185422
186889
  tab: "advanced",
185423
186890
  title: "Snapshot freshness",
186891
+ titleKey: "provider-reolink:snapshotFreshnessSection.s_snapshotFreshness.title",
185424
186892
  description: "When each cached camera reading was last fetched. Groups without a stamp were persisted before per-snapshot tracking — their age is unknown, not fresh. Use \"Refresh from camera\" (General) to re-read an awake camera.",
186893
+ descriptionKey: "provider-reolink:snapshotFreshnessSection.s_snapshotFreshness.description",
185425
186894
  columns: 1,
185426
186895
  fields: [{
185427
186896
  type: "info",
185428
186897
  key: "snapshotFreshness",
185429
186898
  label: "Per-snapshot read times",
186899
+ labelKey: "provider-reolink:field.snapshotFreshness.label",
185430
186900
  content: `${opts.sleeping ? "Camera is asleep — no group can refresh until it wakes; a sleeping battery camera is never woken to read settings.\n" : ""}${header}\n${lines.join("\n")}`
185431
186901
  }]
185432
186902
  };
@@ -186622,7 +188092,7 @@ function capDayNightModeToReolink(mode) {
186622
188092
  case "auto": return "auto";
186623
188093
  case "day": return "color";
186624
188094
  case "night": return "blackAndWhite";
186625
- case "schedule": throw new Error("Reolink cameras do not support a scheduled day/night mode");
188095
+ case "schedule": throw userError("reolink.day-night-schedule-unsupported", void 0, "Reolink cameras do not support a scheduled day/night mode");
186626
188096
  }
186627
188097
  }
186628
188098
  //#endregion
@@ -186917,6 +188387,7 @@ function buildOsdStateBanner(snapshot, opts) {
186917
188387
  type: "info",
186918
188388
  key: "osdSnapshotState",
186919
188389
  label: "OSD state",
188390
+ labelKey: "provider-reolink:osdStateBanner.osdSnapshotState.label",
186920
188391
  variant: "warning",
186921
188392
  content: snapshot === void 0 ? `Camera is asleep and its OSD state has never been read — the toggles below are unknown until it wakes; ${NEVER_WOKEN_SUFFIX}` : snapshot.fetchedAt !== void 0 ? `Camera is asleep — showing the OSD state last read ${new Date(snapshot.fetchedAt).toLocaleString()}. It refreshes when the camera wakes; ${NEVER_WOKEN_SUFFIX}` : `Camera is asleep — showing the last known OSD state (age unknown). It refreshes when the camera wakes; ${NEVER_WOKEN_SUFFIX}`
186922
188393
  };
@@ -186924,6 +188395,7 @@ function buildOsdStateBanner(snapshot, opts) {
186924
188395
  type: "info",
186925
188396
  key: "osdSnapshotState",
186926
188397
  label: "OSD state",
188398
+ labelKey: "provider-reolink:osdStateBanner.osdSnapshotState.label",
186927
188399
  variant: "warning",
186928
188400
  content: "OSD state has not been read from this camera yet — unknown toggles are disabled until a read succeeds."
186929
188401
  };
@@ -186973,7 +188445,9 @@ function buildOsdSection(snapshot, values, opts) {
186973
188445
  id: "osd",
186974
188446
  tab: "image",
186975
188447
  title: "OSD overlay",
188448
+ titleKey: "provider-reolink:osdSection.s_osd.title",
186976
188449
  description: "On-screen overlays burned into the video by the camera. Read with `GetOsdDatetime` (cmd_id 44) and pushed with `SetOsdDatetime` (cmd_id 45). Positions are the four frame corners; an overlay the camera holds somewhere else keeps that placement unless a corner is chosen.",
188450
+ descriptionKey: "provider-reolink:osdSection.s_osd.description",
186977
188451
  columns: 2,
186978
188452
  fields: [
186979
188453
  ...banner ? [banner] : [],
@@ -186983,7 +188457,9 @@ function buildOsdSection(snapshot, values, opts) {
186983
188457
  type: "text",
186984
188458
  key: "osdChannelName",
186985
188459
  label: "Channel name",
188460
+ labelKey: "provider-reolink:fields.osdChannelName.label",
186986
188461
  description: "Text shown in the channel-name overlay.",
188462
+ descriptionKey: "provider-reolink:fields.osdChannelName.description",
186987
188463
  default: values.osdChannelName,
186988
188464
  placeholder: "Front door"
186989
188465
  },
@@ -187466,13 +188942,17 @@ var SirenAccessory = class extends BaseDevice {
187466
188942
  return hydrateSchema({ sections: [{
187467
188943
  id: "siren",
187468
188944
  title: "Siren",
188945
+ titleKey: "provider-reolink:schema.s_siren.title",
187469
188946
  description: "Tone duration when triggered. Set to 0 for \"play until turned off\"; positive values pulse for that many seconds.",
188947
+ descriptionKey: "provider-reolink:schema.s_siren.description",
187470
188948
  columns: 1,
187471
188949
  fields: [{
187472
188950
  type: "number",
187473
188951
  key: "sirenDurationS",
187474
188952
  label: "Default duration (seconds)",
188953
+ labelKey: "provider-reolink:schema.s_siren.sirenDurationS.label",
187475
188954
  description: "Forwarded to `setSiren(on=true, duration)` (cmd_id 30) when the operator flips the switch.",
188955
+ descriptionKey: "provider-reolink:schema.s_siren.sirenDurationS.description",
187476
188956
  default: 0,
187477
188957
  min: 0,
187478
188958
  max: 300,
@@ -187481,13 +188961,17 @@ var SirenAccessory = class extends BaseDevice {
187481
188961
  }, {
187482
188962
  id: "motion-siren",
187483
188963
  title: "Motion-triggered siren",
188964
+ titleKey: "provider-reolink:schema.s_motion-siren.title",
187484
188965
  description: "Detection classes that fire the siren when \"Trigger on motion\" is armed. Forwarded as a `typeScheduleList` to `setSirenOnMotion` (cmd_id 231). Saved values re-apply on every motion-trigger flip.",
188966
+ descriptionKey: "provider-reolink:schema.s_motion-siren.description",
187485
188967
  columns: 1,
187486
188968
  fields: [{
187487
188969
  type: "select",
187488
188970
  key: "motionSirenDetectType",
187489
188971
  label: "Detection classes",
188972
+ labelKey: "provider-reolink:schema.s_motion-siren.motionSirenDetectType.label",
187490
188973
  description: "Object/motion classes that arm the siren. `MD` = generic motion detection.",
188974
+ descriptionKey: "provider-reolink:schema.s_motion-siren.motionSirenDetectType.description",
187491
188975
  options: SIREN_DETECT_TYPE_CHOICES.map((v) => ({
187492
188976
  value: v,
187493
188977
  label: v.replace(/,/g, " + ").replace(/_/g, "+")
@@ -187950,13 +189434,17 @@ var FloodlightAccessory = class extends BaseDevice {
187950
189434
  return hydrateSchema({ sections: [{
187951
189435
  id: "motion-floodlight",
187952
189436
  title: "Motion-triggered floodlight",
189437
+ titleKey: "provider-reolink:schema.s_motion-floodlight.title",
187953
189438
  description: "Pushed to the camera (`FloodlightTask`) when the \"Trigger on motion\" toggle is armed. Saved values also re-apply on every motion-trigger flip so the firmware always honours your preference.",
189439
+ descriptionKey: "provider-reolink:schema.s_motion-floodlight.description",
187954
189440
  columns: 2,
187955
189441
  fields: [{
187956
189442
  type: "number",
187957
189443
  key: "motionLightDurationS",
187958
189444
  label: "Light duration (s)",
189445
+ labelKey: "provider-reolink:schema.s_motion-floodlight.motionLightDurationS.label",
187959
189446
  description: "Seconds the floodlight stays lit after a motion event. Reolink range 1..600s.",
189447
+ descriptionKey: "provider-reolink:schema.s_motion-floodlight.motionLightDurationS.description",
187960
189448
  min: 1,
187961
189449
  max: 600,
187962
189450
  step: 1,
@@ -187965,7 +189453,9 @@ var FloodlightAccessory = class extends BaseDevice {
187965
189453
  type: "select",
187966
189454
  key: "motionLightDetectType",
187967
189455
  label: "Detection classes",
189456
+ labelKey: "provider-reolink:schema.s_motion-floodlight.motionLightDetectType.label",
187968
189457
  description: "Object classes that turn the floodlight on. Composed values (e.g. \"people,vehicle\") fire when ANY listed class detects.",
189458
+ descriptionKey: "provider-reolink:schema.s_motion-floodlight.motionLightDetectType.description",
187969
189459
  options: FLOODLIGHT_DETECT_TYPE_CHOICES.map((v) => ({
187970
189460
  value: v,
187971
189461
  label: v.replace(/,/g, " + ").replace(/_/g, "+")
@@ -188204,14 +189694,18 @@ var PirAccessory = class extends BaseDevice {
188204
189694
  return hydrateSchema({ sections: [{
188205
189695
  id: "pir",
188206
189696
  title: "PIR sensor",
189697
+ titleKey: "provider-reolink:schema.s_pir.title",
188207
189698
  description: "Hardware passive-infrared trigger configuration. Pushed via cmd_id 153 (SetPirInfo).",
189699
+ descriptionKey: "provider-reolink:schema.s_pir.description",
188208
189700
  columns: 2,
188209
189701
  fields: [
188210
189702
  {
188211
189703
  type: "slider",
188212
189704
  key: "pirSensitive",
188213
189705
  label: "Sensitivity",
189706
+ labelKey: "provider-reolink:schema.s_pir.pirSensitive.label",
188214
189707
  description: "Higher value → triggers on smaller motion. Most cameras max at 100.",
189708
+ descriptionKey: "provider-reolink:schema.s_pir.pirSensitive.description",
188215
189709
  min: 1,
188216
189710
  max: 100,
188217
189711
  step: 1,
@@ -188222,6 +189716,7 @@ var PirAccessory = class extends BaseDevice {
188222
189716
  type: "slider",
188223
189717
  key: "pirIntervalS",
188224
189718
  label: "Cooldown (seconds)",
189719
+ labelKey: "provider-reolink:schema.s_pir.pirIntervalS.label",
188225
189720
  description: `Minimum delay between consecutive PIR triggers. Reolink app range 5..${intervalMax}s; firmware clamps on push.`,
188226
189721
  min: 5,
188227
189722
  max: intervalMax,
@@ -188234,7 +189729,9 @@ var PirAccessory = class extends BaseDevice {
188234
189729
  type: "boolean",
188235
189730
  key: "pirReduceAlarm",
188236
189731
  label: "Reduce false alarms",
189732
+ labelKey: "provider-reolink:schema.s_pir.pirReduceAlarm.label",
188237
189733
  description: "Trades detection range for fewer false positives. Maps to firmware `reduceAlarm` flag.",
189734
+ descriptionKey: "provider-reolink:schema.s_pir.pirReduceAlarm.description",
188238
189735
  ...typeof reduceAlarm === "boolean" ? { default: reduceAlarm } : {},
188239
189736
  style: "switch",
188240
189737
  span: 2
@@ -189762,6 +191259,7 @@ function buildSessionsTabSections(snap, opts) {
189762
191259
  type: "info",
189763
191260
  key: "sessionsError",
189764
191261
  label: "Refresh error",
191262
+ labelKey: "provider-reolink:errorBanner.sessionsError.label",
189765
191263
  content: snap.error,
189766
191264
  variant: "danger"
189767
191265
  }] : [];
@@ -189783,6 +191281,7 @@ function buildSessionsTabSections(snap, opts) {
189783
191281
  id: "sessions-status",
189784
191282
  title: opts.title ?? "Sessions",
189785
191283
  description: "Diagnostics for the Baichuan control channel. Auto-refreshes when older than 1 minute; click Refresh to force.",
191284
+ descriptionKey: "provider-reolink:sessionsTabSections.s_sessions-status.description",
189786
191285
  tab: opts.tabId,
189787
191286
  order: 0,
189788
191287
  columns: 1,
@@ -189792,38 +191291,46 @@ function buildSessionsTabSections(snap, opts) {
189792
191291
  type: "info",
189793
191292
  key: "sessionsLastRefreshed",
189794
191293
  label: "Last refreshed",
191294
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsLastRefreshed.label",
189795
191295
  content: ageInfo
189796
191296
  },
189797
191297
  {
189798
191298
  type: "object-array",
189799
191299
  key: "sessionsOnlineUsers",
189800
191300
  label: "Online users (firmware)",
191301
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.label",
189801
191302
  description: "Active user sessions reported by the device (cmd_id 145). Includes other admins, mobile app, NVR linkups.",
191303
+ descriptionKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.description",
189802
191304
  columns: [
189803
191305
  {
189804
191306
  key: "userName",
189805
- label: "User"
191307
+ label: "User",
191308
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.userName.label"
189806
191309
  },
189807
191310
  {
189808
191311
  key: "ip",
189809
191312
  label: "IP",
191313
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.ip.label",
189810
191314
  kind: "monospace",
189811
191315
  width: "160px"
189812
191316
  },
189813
191317
  {
189814
191318
  key: "sessionId",
189815
191319
  label: "Session",
191320
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.sessionId.label",
189816
191321
  kind: "monospace",
189817
191322
  width: "90px"
189818
191323
  },
189819
191324
  {
189820
191325
  key: "level",
189821
191326
  label: "Level",
191327
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.level.label",
189822
191328
  width: "80px"
189823
191329
  },
189824
191330
  {
189825
191331
  key: "status",
189826
191332
  label: "State",
191333
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.status.label",
189827
191334
  kind: "status",
189828
191335
  width: "100px"
189829
191336
  }
@@ -189835,14 +191342,18 @@ function buildSessionsTabSections(snap, opts) {
189835
191342
  type: "object-array",
189836
191343
  key: "sessionsSocketPool",
189837
191344
  label: "Our socket pool",
191345
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsSocketPool.label",
189838
191346
  description: "Open Baichuan TCP/UDP sockets this process holds for the device. One row per pool tag.",
191347
+ descriptionKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsSocketPool.description",
189839
191348
  columns: [{
189840
191349
  key: "tag",
189841
191350
  label: "Tag",
191351
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsSocketPool.tag.label",
189842
191352
  kind: "monospace"
189843
191353
  }, {
189844
191354
  key: "state",
189845
191355
  label: "State",
191356
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsSocketPool.state.label",
189846
191357
  kind: "status",
189847
191358
  width: "100px"
189848
191359
  }],
@@ -189853,10 +191364,13 @@ function buildSessionsTabSections(snap, opts) {
189853
191364
  type: "object-array",
189854
191365
  key: "sessionsDedicated",
189855
191366
  label: "Dedicated lib sessions",
191367
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsDedicated.label",
189856
191368
  description: "Internal lib-side dedicated sessions (streaming/talk channels). Older lib versions leave this list empty.",
191369
+ descriptionKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsDedicated.description",
189857
191370
  columns: [{
189858
191371
  key: "key",
189859
191372
  label: "Session key",
191373
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsDedicated.key.label",
189860
191374
  kind: "monospace"
189861
191375
  }],
189862
191376
  value: dedicatedRows,
@@ -189866,12 +191380,14 @@ function buildSessionsTabSections(snap, opts) {
189866
191380
  type: "info",
189867
191381
  key: "sessionsCooldown",
189868
191382
  label: "Reconnect cooldown",
191383
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsCooldown.label",
189869
191384
  content: cooldownText
189870
191385
  },
189871
191386
  {
189872
191387
  type: "device-action-button",
189873
191388
  key: "_refreshSessions",
189874
191389
  label: "Refresh sessions",
191390
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status._refreshSessions.label",
189875
191391
  buttonLabel: "Refresh now",
189876
191392
  action: "refresh-sessions",
189877
191393
  variant: "default"
@@ -191343,11 +192859,11 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
191343
192859
  });
191344
192860
  if (!this.isEmailPushEligible) {
191345
192861
  this.ctx.logger.warn("email-push: not eligible (hub-attached camera)", { tags: { deviceId: this.id } });
191346
- throw new Error("Email push is not available for hub-attached cameras");
192862
+ throw userError("reolink.email-push-hub-attached", void 0, "Email push is not available for hub-attached cameras");
191347
192863
  }
191348
192864
  if (!this.emailPushParamsResolver) {
191349
192865
  this.ctx.logger.warn("email-push: resolver not wired — restart the Reolink addon", { tags: { deviceId: this.id } });
191350
- throw new Error("Email Push Server is not wired — restart the Reolink addon");
192866
+ throw userError("reolink.email-push-not-wired", void 0, "Email Push Server is not wired — restart the Reolink addon");
191351
192867
  }
191352
192868
  const params = await this.emailPushParamsResolver();
191353
192869
  if (!params) {
@@ -191406,14 +192922,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
191406
192922
  this.ctx.logger.warn("baichuan-webhook: not eligible", { tags: { deviceId: this.id } });
191407
192923
  throw new Error("HaCfg webhook Auto-configure is only available on standalone battery cameras");
191408
192924
  }
191409
- if (!this.baichuanWebhookParamsResolver) throw new Error("HaCfg Webhook Server is not wired — restart the Reolink addon");
192925
+ if (!this.baichuanWebhookParamsResolver) throw userError("reolink.webhook-not-wired", void 0, "HaCfg Webhook Server is not wired — restart the Reolink addon");
191410
192926
  const params = await this.baichuanWebhookParamsResolver();
191411
192927
  if (!params) throw new Error("Enable the HaCfg Webhook Server in the Reolink addon settings, and ensure this host has a reachable LAN address.");
191412
192928
  await this.wakeIfSleeping();
191413
192929
  const api = await this.ensureApi();
191414
192930
  const supported = await api.probeBaichuanWebhookSupport();
191415
192931
  this.baichuanWebhookSupportedCached = supported;
191416
- if (!supported) throw new Error("Camera firmware does not support Baichuan HaCfg webhook (cmd 806)");
192932
+ if (!supported) throw userError("reolink.webhook-unsupported", void 0, "Camera firmware does not support Baichuan HaCfg webhook (cmd 806)");
191417
192933
  this.ctx.logger.info("baichuan-webhook: auto-configuring camera", {
191418
192934
  tags: { deviceId: this.id },
191419
192935
  meta: { url: params.url }
@@ -192092,26 +193608,32 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192092
193608
  id: "sleep-push",
192093
193609
  tab: "motion",
192094
193610
  title: "Battery motion (sleep push)",
193611
+ titleKey: "provider-reolink:emailPushSection.s_sleep-push.title",
192095
193612
  description: "Battery cameras sleep and can't be polled for motion. Prefer HaCfg webhook when the firmware supports it (cmd 806); otherwise use Email/SMTP. Enable the matching server under Integrations → Reolink, then Auto-configure. Only one path should be armed — the preference below gates which inbound source is accepted.",
193613
+ descriptionKey: "provider-reolink:emailPushSection.s_sleep-push.description",
192096
193614
  columns: 1,
192097
193615
  fields: [
192098
193616
  {
192099
193617
  type: "select",
192100
193618
  key: "batterySleepPushMethod",
192101
193619
  label: "Preferred sleep-push path",
193620
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push.batterySleepPushMethod.label",
192102
193621
  default: "auto",
192103
193622
  options: [
192104
193623
  {
192105
193624
  value: "auto",
192106
- label: "Auto (webhook if supported, else email)"
193625
+ label: "Auto (webhook if supported, else email)",
193626
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push.batterySleepPushMethod.o_auto.label"
192107
193627
  },
192108
193628
  {
192109
193629
  value: "webhook",
192110
- label: "HaCfg webhook only"
193630
+ label: "HaCfg webhook only",
193631
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push.batterySleepPushMethod.o_webhook.label"
192111
193632
  },
192112
193633
  {
192113
193634
  value: "email",
192114
- label: "Email/SMTP only"
193635
+ label: "Email/SMTP only",
193636
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push.batterySleepPushMethod.o_email.label"
192115
193637
  }
192116
193638
  ]
192117
193639
  },
@@ -192119,6 +193641,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192119
193641
  type: "device-action-button",
192120
193642
  key: "_autoConfigureBaichuanWebhook",
192121
193643
  label: "HaCfg webhook",
193644
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push._autoConfigureBaichuanWebhook.label",
192122
193645
  buttonLabel: "Auto-configure HaCfg webhook",
192123
193646
  action: "auto-configure-baichuan-webhook",
192124
193647
  variant: "default"
@@ -192127,6 +193650,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192127
193650
  type: "device-action-button",
192128
193651
  key: "_autoConfigureEmailPush",
192129
193652
  label: "Email push",
193653
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push._autoConfigureEmailPush.label",
192130
193654
  buttonLabel: "Auto-configure email push",
192131
193655
  action: "auto-configure-email-push",
192132
193656
  variant: "default"
@@ -192150,19 +193674,23 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192150
193674
  id: "email-smtp",
192151
193675
  tab: "email",
192152
193676
  title: "SMTP server",
193677
+ titleKey: "provider-reolink:emailTabSections.s_email-smtp.title",
192153
193678
  description: "The camera's own outgoing-mail (SMTP) settings. Edit and save to write them to the camera, or use \"Auto-configure email push\" under Motion to point it at the built-in server. Leave the password blank to keep the current one.",
193679
+ descriptionKey: "provider-reolink:emailTabSections.s_email-smtp.description",
192154
193680
  columns: 2,
192155
193681
  fields: [
192156
193682
  {
192157
193683
  type: "text",
192158
193684
  key: "emailSmtpServer",
192159
193685
  label: "SMTP server",
193686
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailSmtpServer.label",
192160
193687
  placeholder: "smtp.example.com"
192161
193688
  },
192162
193689
  {
192163
193690
  type: "number",
192164
193691
  key: "emailSmtpPort",
192165
193692
  label: "SMTP port",
193693
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailSmtpPort.label",
192166
193694
  min: 1,
192167
193695
  max: 65535,
192168
193696
  step: 1
@@ -192171,12 +193699,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192171
193699
  type: "text",
192172
193700
  key: "emailUserName",
192173
193701
  label: "Username / sender",
193702
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailUserName.label",
192174
193703
  placeholder: "cam@example.com"
192175
193704
  },
192176
193705
  {
192177
193706
  type: "password",
192178
193707
  key: "emailPassword",
192179
193708
  label: "Password",
193709
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailPassword.label",
192180
193710
  showToggle: true,
192181
193711
  placeholder: "leave blank to keep"
192182
193712
  },
@@ -192184,12 +193714,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192184
193714
  type: "boolean",
192185
193715
  key: "emailSsl",
192186
193716
  label: "SSL/TLS",
193717
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailSsl.label",
192187
193718
  style: "switch"
192188
193719
  },
192189
193720
  {
192190
193721
  type: "text",
192191
193722
  key: "emailSendNickname",
192192
- label: "Sender name"
193723
+ label: "Sender name",
193724
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailSendNickname.label"
192193
193725
  }
192194
193726
  ]
192195
193727
  },
@@ -192197,24 +193729,29 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192197
193729
  id: "email-recipients",
192198
193730
  tab: "email",
192199
193731
  title: "Recipients",
193732
+ titleKey: "provider-reolink:emailTabSections.s_email-recipients.title",
192200
193733
  description: "Up to three recipient addresses the camera sends motion mail to.",
193734
+ descriptionKey: "provider-reolink:emailTabSections.s_email-recipients.description",
192201
193735
  columns: 1,
192202
193736
  fields: [
192203
193737
  {
192204
193738
  type: "text",
192205
193739
  key: "emailRecipient1",
192206
193740
  label: "Recipient 1",
193741
+ labelKey: "provider-reolink:emailTabSections.s_email-recipients.emailRecipient1.label",
192207
193742
  placeholder: "alerts@example.com"
192208
193743
  },
192209
193744
  {
192210
193745
  type: "text",
192211
193746
  key: "emailRecipient2",
192212
- label: "Recipient 2"
193747
+ label: "Recipient 2",
193748
+ labelKey: "provider-reolink:emailTabSections.s_email-recipients.emailRecipient2.label"
192213
193749
  },
192214
193750
  {
192215
193751
  type: "text",
192216
193752
  key: "emailRecipient3",
192217
- label: "Recipient 3"
193753
+ label: "Recipient 3",
193754
+ labelKey: "provider-reolink:emailTabSections.s_email-recipients.emailRecipient3.label"
192218
193755
  }
192219
193756
  ]
192220
193757
  },
@@ -192222,12 +193759,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192222
193759
  id: "email-schedule",
192223
193760
  tab: "email",
192224
193761
  title: "Alarm schedule",
193762
+ titleKey: "provider-reolink:emailTabSections.s_email-schedule.title",
192225
193763
  description: "Master enable for the camera's email alarm schedule (EmailTask).",
193764
+ descriptionKey: "provider-reolink:emailTabSections.s_email-schedule.description",
192226
193765
  columns: 1,
192227
193766
  fields: [{
192228
193767
  type: "boolean",
192229
193768
  key: "emailScheduleEnabled",
192230
193769
  label: "Email alarm schedule enabled",
193770
+ labelKey: "provider-reolink:emailTabSections.s_email-schedule.emailScheduleEnabled.label",
192231
193771
  style: "switch"
192232
193772
  }]
192233
193773
  }
@@ -194455,7 +195995,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
194455
195995
  const api = await this.ensureApi();
194456
195996
  const channel = this.getChannel();
194457
195997
  const id = Number(presetId);
194458
- if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
195998
+ if (!Number.isFinite(id)) throw userError("reolink.preset-invalid", { presetId: String(presetId) }, `Invalid presetId: ${presetId}`);
194459
195999
  await api.moveToPtzPreset(channel, id);
194460
196000
  },
194461
196001
  savePreset: async ({ deviceId, presetId, name }) => {
@@ -194463,7 +196003,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
194463
196003
  const api = await this.ensureApi();
194464
196004
  const channel = this.getChannel();
194465
196005
  const id = Number(presetId);
194466
- if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
196006
+ if (!Number.isFinite(id)) throw userError("reolink.preset-invalid", { presetId: String(presetId) }, `Invalid presetId: ${presetId}`);
194467
196007
  await api.setPtzPreset(channel, id, name);
194468
196008
  },
194469
196009
  deletePreset: async ({ deviceId, presetId }) => {
@@ -194471,7 +196011,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
194471
196011
  const api = await this.ensureApi();
194472
196012
  const channel = this.getChannel();
194473
196013
  const id = Number(presetId);
194474
- if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
196014
+ if (!Number.isFinite(id)) throw userError("reolink.preset-invalid", { presetId: String(presetId) }, `Invalid presetId: ${presetId}`);
194475
196015
  await api.deletePtzPreset(channel, id);
194476
196016
  },
194477
196017
  getOptions: async ({ deviceId }) => {
@@ -194523,7 +196063,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
194523
196063
  }
194524
196064
  if (home.source === "none" || home.presetId === null) throw new Error("No home preset configured for this camera, and it has no guard point set — set one in the PTZ settings, or place a guard point on the camera");
194525
196065
  const id = Number(home.presetId);
194526
- if (!Number.isFinite(id)) throw new Error(`Configured home preset is not a Baichuan id: ${home.presetId}`);
196066
+ if (!Number.isFinite(id)) throw userError("reolink.home-preset-not-baichuan", { presetId: String(home.presetId) }, `Configured home preset is not a Baichuan id: ${home.presetId}`);
194527
196067
  const api = await this.ensureApi();
194528
196068
  const channel = this.getChannel();
194529
196069
  await api.moveToPtzPreset(channel, id);
@@ -196095,12 +197635,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196095
197635
  id: "driver-refresh",
196096
197636
  tab: "general",
196097
197637
  title: "Camera data",
197638
+ titleKey: "provider-reolink:schema.s_driver-refresh.title",
196098
197639
  description: "Go and ask the camera now. Every other read here is deliberately cheap about a battery camera — it serves what it already knows and refreshes quietly behind you, and never wakes a sleeping camera just because you opened this tab. This button is the exception: it wakes the camera, re-reads everything, and tells you what it got. Use it after changing settings from the Reolink app, or whenever you want to be sure the form is showing the camera and not a memory of it.",
197640
+ descriptionKey: "provider-reolink:schema.s_driver-refresh.description",
196099
197641
  columns: 1,
196100
197642
  fields: [{
196101
197643
  type: "device-action-button",
196102
197644
  key: "_refreshSettings",
196103
197645
  label: "Refresh from camera",
197646
+ labelKey: "provider-reolink:schema.s_driver-refresh._refreshSettings.label",
196104
197647
  buttonLabel: "Refresh now",
196105
197648
  action: "refresh-settings",
196106
197649
  variant: "default",
@@ -196111,13 +197654,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196111
197654
  id: "connection",
196112
197655
  tab: "general",
196113
197656
  title: "Connection",
197657
+ titleKey: "provider-reolink:schema.s_connection.title",
196114
197658
  description: "Baichuan protocol connection settings.",
197659
+ descriptionKey: "provider-reolink:schema.s_connection.description",
196115
197660
  columns: 2,
196116
197661
  fields: [
196117
197662
  {
196118
197663
  type: "text",
196119
197664
  key: "host",
196120
197665
  label: "Camera IP",
197666
+ labelKey: "provider-reolink:schema.s_connection.host.label",
196121
197667
  required: true,
196122
197668
  placeholder: "192.168.10.100"
196123
197669
  },
@@ -196125,6 +197671,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196125
197671
  type: "number",
196126
197672
  key: "port",
196127
197673
  label: "Port",
197674
+ labelKey: "provider-reolink:schema.s_connection.port.label",
196128
197675
  default: 9e3,
196129
197676
  min: 1,
196130
197677
  max: 65535,
@@ -196134,12 +197681,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196134
197681
  type: "text",
196135
197682
  key: "username",
196136
197683
  label: "Username",
197684
+ labelKey: "provider-reolink:schema.s_connection.username.label",
196137
197685
  default: "admin"
196138
197686
  },
196139
197687
  {
196140
197688
  type: "password",
196141
197689
  key: "password",
196142
197690
  label: "Password",
197691
+ labelKey: "provider-reolink:schema.s_connection.password.label",
196143
197692
  required: true,
196144
197693
  showToggle: true
196145
197694
  }
@@ -196149,13 +197698,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196149
197698
  id: "network-ports",
196150
197699
  tab: "network",
196151
197700
  title: "Service ports",
197701
+ titleKey: "provider-reolink:schema.s_network-ports.title",
196152
197702
  description: "Per-protocol enable toggle + port number, pushed via Baichuan `SetNetPort` (cmd_id 36). The Baichuan control port is the Connection → Port field. Changing a port number breaks existing URLs for that protocol until consumers are reconfigured.",
197703
+ descriptionKey: "provider-reolink:schema.s_network-ports.description",
196153
197704
  columns: 2,
196154
197705
  fields: [
196155
197706
  {
196156
197707
  type: "boolean",
196157
197708
  key: "netHttpEnable",
196158
197709
  label: "HTTP enabled",
197710
+ labelKey: "provider-reolink:schema.s_network-ports.netHttpEnable.label",
196159
197711
  default: netSnap.http?.enable ?? true,
196160
197712
  style: "switch"
196161
197713
  },
@@ -196163,6 +197715,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196163
197715
  type: "number",
196164
197716
  key: "netHttpPort",
196165
197717
  label: "HTTP port",
197718
+ labelKey: "provider-reolink:schema.s_network-ports.netHttpPort.label",
196166
197719
  default: netSnap.http?.port ?? 80,
196167
197720
  min: 1,
196168
197721
  max: 65535,
@@ -196172,6 +197725,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196172
197725
  type: "boolean",
196173
197726
  key: "netHttpsEnable",
196174
197727
  label: "HTTPS enabled",
197728
+ labelKey: "provider-reolink:schema.s_network-ports.netHttpsEnable.label",
196175
197729
  default: netSnap.https?.enable ?? false,
196176
197730
  style: "switch"
196177
197731
  },
@@ -196179,6 +197733,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196179
197733
  type: "number",
196180
197734
  key: "netHttpsPort",
196181
197735
  label: "HTTPS port",
197736
+ labelKey: "provider-reolink:schema.s_network-ports.netHttpsPort.label",
196182
197737
  default: netSnap.https?.port ?? 443,
196183
197738
  min: 1,
196184
197739
  max: 65535,
@@ -196188,6 +197743,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196188
197743
  type: "boolean",
196189
197744
  key: "netRtspEnable",
196190
197745
  label: "RTSP enabled",
197746
+ labelKey: "provider-reolink:schema.s_network-ports.netRtspEnable.label",
196191
197747
  default: netSnap.rtsp?.enable ?? true,
196192
197748
  style: "switch"
196193
197749
  },
@@ -196195,6 +197751,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196195
197751
  type: "number",
196196
197752
  key: "netRtspPort",
196197
197753
  label: "RTSP port",
197754
+ labelKey: "provider-reolink:schema.s_network-ports.netRtspPort.label",
196198
197755
  default: netSnap.rtsp?.port ?? 554,
196199
197756
  min: 1,
196200
197757
  max: 65535,
@@ -196204,6 +197761,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196204
197761
  type: "boolean",
196205
197762
  key: "netRtmpEnable",
196206
197763
  label: "RTMP enabled",
197764
+ labelKey: "provider-reolink:schema.s_network-ports.netRtmpEnable.label",
196207
197765
  default: netSnap.rtmp?.enable ?? true,
196208
197766
  style: "switch"
196209
197767
  },
@@ -196211,6 +197769,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196211
197769
  type: "number",
196212
197770
  key: "netRtmpPort",
196213
197771
  label: "RTMP port",
197772
+ labelKey: "provider-reolink:schema.s_network-ports.netRtmpPort.label",
196214
197773
  default: netSnap.rtmp?.port ?? 1935,
196215
197774
  min: 1,
196216
197775
  max: 65535,
@@ -196220,6 +197779,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196220
197779
  type: "boolean",
196221
197780
  key: "netOnvifEnable",
196222
197781
  label: "ONVIF enabled",
197782
+ labelKey: "provider-reolink:schema.s_network-ports.netOnvifEnable.label",
196223
197783
  default: netSnap.onvif?.enable ?? true,
196224
197784
  style: "switch"
196225
197785
  },
@@ -196227,6 +197787,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196227
197787
  type: "number",
196228
197788
  key: "netOnvifPort",
196229
197789
  label: "ONVIF port",
197790
+ labelKey: "provider-reolink:schema.s_network-ports.netOnvifPort.label",
196230
197791
  default: netSnap.onvif?.port ?? 8e3,
196231
197792
  min: 1,
196232
197793
  max: 65535,
@@ -196238,14 +197799,18 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196238
197799
  id: "time-ntp",
196239
197800
  tab: "network",
196240
197801
  title: "Time / NTP",
197802
+ titleKey: "provider-reolink:schema.s_time-ntp.title",
196241
197803
  description: "Network time synchronisation. The camera-side push goes through the native Baichuan NTP commands; until those land in the driver lib the values persist to the driver config and apply on the next lib update.",
197804
+ descriptionKey: "provider-reolink:schema.s_time-ntp.description",
196242
197805
  columns: 2,
196243
197806
  fields: [
196244
197807
  {
196245
197808
  type: "timezone",
196246
197809
  key: "timeZoneId",
196247
197810
  label: "Timezone",
197811
+ labelKey: "provider-reolink:schema.s_time-ntp.timeZoneId.label",
196248
197812
  description: "Camera timezone. Selecting a zone pushes its standard UTC offset (Baichuan `SetSystemGeneral`, cmd 105) and DST rule (`SetDst`, cmd 107) to the camera — DST is handled automatically by the chosen zone, so summer/winter clocks track correctly without a separate toggle.",
197813
+ descriptionKey: "provider-reolink:schema.s_time-ntp.timeZoneId.description",
196249
197814
  default: this.config.get("timeZoneId") ?? reolinkTimeZoneToIana(this.config.get("deviceCache")?.systemGeneralSnapshot?.timeZone) ?? "",
196250
197815
  span: 2
196251
197816
  },
@@ -196253,6 +197818,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196253
197818
  type: "boolean",
196254
197819
  key: "ntpEnabled",
196255
197820
  label: "NTP sync",
197821
+ labelKey: "provider-reolink:schema.s_time-ntp.ntpEnabled.label",
196256
197822
  default: ntpSnap.enabled ?? true,
196257
197823
  style: "switch",
196258
197824
  span: 2
@@ -196261,6 +197827,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196261
197827
  type: "text",
196262
197828
  key: "ntpServer",
196263
197829
  label: "NTP server",
197830
+ labelKey: "provider-reolink:schema.s_time-ntp.ntpServer.label",
196264
197831
  default: ntpSnap.server ?? "pool.ntp.org",
196265
197832
  placeholder: "pool.ntp.org",
196266
197833
  span: 2
@@ -196269,6 +197836,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196269
197836
  type: "number",
196270
197837
  key: "ntpPort",
196271
197838
  label: "NTP port",
197839
+ labelKey: "provider-reolink:schema.s_time-ntp.ntpPort.label",
196272
197840
  default: ntpSnap.port ?? 123,
196273
197841
  min: 1,
196274
197842
  max: 65535,
@@ -196278,6 +197846,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196278
197846
  type: "number",
196279
197847
  key: "ntpInterval",
196280
197848
  label: "Sync interval (min)",
197849
+ labelKey: "provider-reolink:schema.s_time-ntp.ntpInterval.label",
196281
197850
  default: ntpSnap.interval ?? 1440,
196282
197851
  min: 1,
196283
197852
  max: 10080,
@@ -196289,13 +197858,17 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196289
197858
  id: "motion",
196290
197859
  tab: "motion",
196291
197860
  title: "On-camera motion (MdAlarm)",
197861
+ titleKey: "provider-reolink:schema.s_motion.title",
196292
197862
  description: "Camera-side motion detection master switch + sensitivity. Pushed via Baichuan `SetMdAlarm` (cmd_id 47). Range 1..50 matches the Reolink mobile app + web UI; the driver inverts to the camera-side raw value (`51 - value`) before push.",
197863
+ descriptionKey: "provider-reolink:schema.s_motion.description",
196293
197864
  columns: 2,
196294
197865
  fields: [{
196295
197866
  type: "boolean",
196296
197867
  key: "motionEnabled",
196297
197868
  label: "Motion detection",
197869
+ labelKey: "provider-reolink:schema.s_motion.motionEnabled.label",
196298
197870
  description: "Master switch for the camera's on-board motion detector. Disabling stops the camera from emitting motion events on the alarm channel.",
197871
+ descriptionKey: "provider-reolink:schema.s_motion.motionEnabled.description",
196299
197872
  default: motionSnap.enabled ?? true,
196300
197873
  style: "switch",
196301
197874
  span: 2
@@ -196303,7 +197876,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196303
197876
  type: "slider",
196304
197877
  key: "motionSensitivity",
196305
197878
  label: "Sensitivity",
197879
+ labelKey: "provider-reolink:schema.s_motion.motionSensitivity.label",
196306
197880
  description: "Higher = triggers on smaller motion. Reolink user-facing range 1..50.",
197881
+ descriptionKey: "provider-reolink:schema.s_motion.motionSensitivity.description",
196307
197882
  min: 1,
196308
197883
  max: 50,
196309
197884
  step: 1,
@@ -196317,7 +197892,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196317
197892
  id: "ai-sensitivity",
196318
197893
  tab: "motion",
196319
197894
  title: "AI detection sensitivity",
197895
+ titleKey: "provider-reolink:schema.s_ai-sensitivity.title",
196320
197896
  description: "Per-class on-camera AI sensitivity, stay time (dwell) + min/max target-size gates. Pushed via `SetAiAlarm` (cmd_id 343) in one consolidated write. Sensitivity range 0..100; stay time is the seconds the camera keeps the alarm asserted after the object leaves; target-size gates are the fraction of the frame (0..1) an object must occupy to be considered. Each group appears only when the camera advertises the corresponding detector.",
197897
+ descriptionKey: "provider-reolink:schema.s_ai-sensitivity.description",
196321
197898
  columns: 2,
196322
197899
  fields: [
196323
197900
  [
@@ -196448,13 +198025,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196448
198025
  id: "image",
196449
198026
  tab: "image",
196450
198027
  title: "Image",
198028
+ titleKey: "provider-reolink:schema.s_image.title",
196451
198029
  description: "Core image settings pushed via Baichuan `SetImage` (cmd_id 25). Each slider rearms the camera on save; defaults seed from a `GetVideoInput` probe at boot (refreshed on every `applySettingsPatch`). Range 0..255 — most Reolink models clamp to 0..100 internally.",
198030
+ descriptionKey: "provider-reolink:schema.s_image.description",
196452
198031
  columns: 2,
196453
198032
  fields: [
196454
198033
  {
196455
198034
  type: "slider",
196456
198035
  key: "imgBright",
196457
198036
  label: "Brightness",
198037
+ labelKey: "provider-reolink:schema.s_image.imgBright.label",
196458
198038
  min: 0,
196459
198039
  max: 255,
196460
198040
  step: 1,
@@ -196466,6 +198046,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196466
198046
  type: "slider",
196467
198047
  key: "imgContrast",
196468
198048
  label: "Contrast",
198049
+ labelKey: "provider-reolink:schema.s_image.imgContrast.label",
196469
198050
  min: 0,
196470
198051
  max: 255,
196471
198052
  step: 1,
@@ -196477,6 +198058,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196477
198058
  type: "slider",
196478
198059
  key: "imgSaturation",
196479
198060
  label: "Saturation",
198061
+ labelKey: "provider-reolink:schema.s_image.imgSaturation.label",
196480
198062
  min: 0,
196481
198063
  max: 255,
196482
198064
  step: 1,
@@ -196488,6 +198070,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196488
198070
  type: "slider",
196489
198071
  key: "imgHue",
196490
198072
  label: "Hue",
198073
+ labelKey: "provider-reolink:schema.s_image.imgHue.label",
196491
198074
  min: 0,
196492
198075
  max: 255,
196493
198076
  step: 1,
@@ -196499,6 +198082,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196499
198082
  type: "slider",
196500
198083
  key: "imgSharpen",
196501
198084
  label: "Sharpness",
198085
+ labelKey: "provider-reolink:schema.s_image.imgSharpen.label",
196502
198086
  min: 0,
196503
198087
  max: 255,
196504
198088
  step: 1,
@@ -196513,19 +198097,25 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196513
198097
  id: "isp",
196514
198098
  tab: "image",
196515
198099
  title: "Advanced (ISP)",
198100
+ titleKey: "provider-reolink:schema.s_isp.title",
196516
198101
  description: "Day/Night + exposure + HDR. Pushed via `SetIsp` (cmd_id 25). The day/night threshold uses cmd_id 297 (DayNightThreshold).",
198102
+ descriptionKey: "provider-reolink:schema.s_isp.description",
196517
198103
  columns: 2,
196518
198104
  fields: [{
196519
198105
  type: "text",
196520
198106
  key: "ispDayNight",
196521
198107
  label: "Day/Night mode",
198108
+ labelKey: "provider-reolink:schema.s_isp.ispDayNight.label",
196522
198109
  description: "Camera-specific values — common: `auto`, `color`, `blackAndWhite`. The driver normalizes case before push.",
198110
+ descriptionKey: "provider-reolink:schema.s_isp.ispDayNight.description",
196523
198111
  default: imgSnap.dayNight ?? "auto"
196524
198112
  }, {
196525
198113
  type: "text",
196526
198114
  key: "ispExposure",
196527
198115
  label: "Exposure",
198116
+ labelKey: "provider-reolink:schema.s_isp.ispExposure.label",
196528
198117
  description: "Common values: `auto`, `manual`. Lowercased before push.",
198118
+ descriptionKey: "provider-reolink:schema.s_isp.ispExposure.description",
196529
198119
  default: "auto"
196530
198120
  }]
196531
198121
  }] : [],
@@ -196533,26 +198123,32 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196533
198123
  id: "ir-lights",
196534
198124
  tab: "image",
196535
198125
  title: "IR + status LED",
198126
+ titleKey: "provider-reolink:schema.s_ir-lights.title",
196536
198127
  description: "IR LED control + per-camera IR brightness. Pushed via `SetIrLights` (cmd_id 209). State `Auto` lets the camera switch IR on automatically at low light; `Off` forces colour-only mode.",
198128
+ descriptionKey: "provider-reolink:schema.s_ir-lights.description",
196537
198129
  columns: 2,
196538
198130
  fields: [
196539
198131
  {
196540
198132
  type: "select",
196541
198133
  key: "irLightsState",
196542
198134
  label: "IR LED",
198135
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsState.label",
196543
198136
  default: "Auto",
196544
198137
  options: [
196545
198138
  {
196546
198139
  value: "Auto",
196547
- label: "Auto (camera decides)"
198140
+ label: "Auto (camera decides)",
198141
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsState.o_Auto.label"
196548
198142
  },
196549
198143
  {
196550
198144
  value: "On",
196551
- label: "Always on"
198145
+ label: "Always on",
198146
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsState.o_On.label"
196552
198147
  },
196553
198148
  {
196554
198149
  value: "Off",
196555
- label: "Always off"
198150
+ label: "Always off",
198151
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsState.o_Off.label"
196556
198152
  }
196557
198153
  ]
196558
198154
  },
@@ -196560,7 +198156,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196560
198156
  type: "slider",
196561
198157
  key: "irLightsBrightness",
196562
198158
  label: "IR brightness",
198159
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsBrightness.label",
196563
198160
  description: "Only applied when the camera advertises programmable IR brightness. 0..255 raw range; many models clamp to 0..100.",
198161
+ descriptionKey: "provider-reolink:schema.s_ir-lights.irLightsBrightness.description",
196564
198162
  min: 0,
196565
198163
  max: 255,
196566
198164
  step: 5,
@@ -196572,7 +198170,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196572
198170
  type: "boolean",
196573
198171
  key: "statusLedEnabled",
196574
198172
  label: "Status LED",
198173
+ labelKey: "provider-reolink:schema.s_ir-lights.statusLedEnabled.label",
196575
198174
  description: "The visible status/power LED on the camera body. Pushed via `SetIrLights` (cmd_id 209) as `lightState`.",
198175
+ descriptionKey: "provider-reolink:schema.s_ir-lights.statusLedEnabled.description",
196576
198176
  default: cache?.ledSnapshot?.statusEnabled ?? true,
196577
198177
  style: "switch",
196578
198178
  span: cache?.ledSnapshot?.doorbellSupported === true ? 1 : 2
@@ -196581,7 +198181,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196581
198181
  type: "boolean",
196582
198182
  key: "doorbellLedEnabled",
196583
198183
  label: "Doorbell LED",
198184
+ labelKey: "provider-reolink:schema.s_ir-lights.doorbellLedEnabled.label",
196584
198185
  description: "The doorbell button ring LED. Pushed via `SetIrLights` (cmd_id 209) as `doorbellLightState`.",
198186
+ descriptionKey: "provider-reolink:schema.s_ir-lights.doorbellLedEnabled.description",
196585
198187
  default: cache?.ledSnapshot?.doorbellEnabled ?? true,
196586
198188
  style: "switch"
196587
198189
  }] : []
@@ -196595,12 +198197,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196595
198197
  id: "auto-focus",
196596
198198
  tab: "image",
196597
198199
  title: "Auto focus",
198200
+ titleKey: "provider-reolink:schema.s_auto-focus.title",
196598
198201
  description: "Toggles the lens auto-focus subsystem. Pushed via `SetAutoFocus` (cmd_id 225). Disabling locks the lens at its current focal position — useful for fixed-distance scenes where AF would hunt.",
198202
+ descriptionKey: "provider-reolink:schema.s_auto-focus.description",
196599
198203
  columns: 1,
196600
198204
  fields: [{
196601
198205
  type: "boolean",
196602
198206
  key: "autoFocusEnabled",
196603
198207
  label: "Auto focus",
198208
+ labelKey: "provider-reolink:schema.s_auto-focus.autoFocusEnabled.label",
196604
198209
  default: cache.autoFocusSnapshot?.enabled ?? true,
196605
198210
  style: "switch"
196606
198211
  }]
@@ -196609,13 +198214,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196609
198214
  id: "audio",
196610
198215
  tab: "audio",
196611
198216
  title: "Audio output",
198217
+ titleKey: "provider-reolink:schema.s_audio.title",
196612
198218
  description: "Speaker volumes pushed via `SetAudioCfg` (cmd_id 265). All ranges 0..100. Doorbell models expose the visitor / talkAndReply channels — non-doorbell cameras ignore those without erroring.",
198219
+ descriptionKey: "provider-reolink:schema.s_audio.description",
196613
198220
  columns: 2,
196614
198221
  fields: [
196615
198222
  {
196616
198223
  type: "slider",
196617
198224
  key: "audioVolume",
196618
198225
  label: "Speaker volume",
198226
+ labelKey: "provider-reolink:schema.s_audio.audioVolume.label",
196619
198227
  min: 0,
196620
198228
  max: 100,
196621
198229
  step: 5,
@@ -196627,7 +198235,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196627
198235
  type: "slider",
196628
198236
  key: "audioTalkAndReplyVolume",
196629
198237
  label: "Talk + reply volume",
198238
+ labelKey: "provider-reolink:schema.s_audio.audioTalkAndReplyVolume.label",
196630
198239
  description: "Two-way audio + canned reply playback. Doorbell-only on most firmwares.",
198240
+ descriptionKey: "provider-reolink:schema.s_audio.audioTalkAndReplyVolume.description",
196631
198241
  min: 0,
196632
198242
  max: 100,
196633
198243
  step: 5,
@@ -196639,7 +198249,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196639
198249
  type: "slider",
196640
198250
  key: "audioVisitorVolume",
196641
198251
  label: "Visitor volume",
198252
+ labelKey: "provider-reolink:schema.s_audio.audioVisitorVolume.label",
196642
198253
  description: "Visitor / chime playback channel. Doorbell-only on most firmwares.",
198254
+ descriptionKey: "provider-reolink:schema.s_audio.audioVisitorVolume.description",
196643
198255
  min: 0,
196644
198256
  max: 100,
196645
198257
  step: 5,
@@ -196654,13 +198266,17 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196654
198266
  id: "audio-noise",
196655
198267
  tab: "audio",
196656
198268
  title: "Audio noise reduction",
198269
+ titleKey: "provider-reolink:schema.s_audio-noise.title",
196657
198270
  description: "On-camera AI denoise applied to the encoded audio track. Pushed via `SetAudioNoise` (cmd_id 440). 0 disables; 1..3 maps to the Reolink mobile app levels (low / mid / high).",
198271
+ descriptionKey: "provider-reolink:schema.s_audio-noise.description",
196658
198272
  columns: 1,
196659
198273
  fields: [{
196660
198274
  type: "slider",
196661
198275
  key: "audioNoiseLevel",
196662
198276
  label: "Noise reduction level",
198277
+ labelKey: "provider-reolink:schema.s_audio-noise.audioNoiseLevel.label",
196663
198278
  description: "0 = off · 1 = low · 2 = medium · 3 = high.",
198279
+ descriptionKey: "provider-reolink:schema.s_audio-noise.audioNoiseLevel.description",
196664
198280
  min: 0,
196665
198281
  max: 3,
196666
198282
  step: 1,
@@ -196673,14 +198289,18 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196673
198289
  id: "intercom",
196674
198290
  tab: "audio",
196675
198291
  title: "Intercom",
198292
+ titleKey: "provider-reolink:schema.s_intercom.title",
196676
198293
  description: "Push-to-talk pipeline tuning. Settings take effect on the next talk session — restart any active session for changes to apply.",
198294
+ descriptionKey: "provider-reolink:schema.s_intercom.description",
196677
198295
  columns: 2,
196678
198296
  fields: [
196679
198297
  {
196680
198298
  type: "slider",
196681
198299
  key: "intercomBlocksPerPayload",
196682
198300
  label: "Blocks per payload",
198301
+ labelKey: "provider-reolink:schema.s_intercom.intercomBlocksPerPayload.label",
196683
198302
  description: "Lower = lower latency (more sendAudio calls/sec). Higher = fewer calls, less per-call socket overhead. Range 1..8. Default 1 (lowest latency).",
198303
+ descriptionKey: "provider-reolink:schema.s_intercom.intercomBlocksPerPayload.description",
196684
198304
  min: 1,
196685
198305
  max: 8,
196686
198306
  step: 1,
@@ -196692,7 +198312,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196692
198312
  type: "slider",
196693
198313
  key: "intercomMaxBacklogMs",
196694
198314
  label: "Max backlog (ms)",
198315
+ labelKey: "provider-reolink:schema.s_intercom.intercomMaxBacklogMs.label",
196695
198316
  description: "Maximum PCM backlog before dropping oldest samples to cap latency. Higher = more stable on slow systems but more latency. Range 20..5000. Default 120.",
198317
+ descriptionKey: "provider-reolink:schema.s_intercom.intercomMaxBacklogMs.description",
196696
198318
  min: 20,
196697
198319
  max: 5e3,
196698
198320
  step: 20,
@@ -196704,7 +198326,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196704
198326
  type: "slider",
196705
198327
  key: "intercomGain",
196706
198328
  label: "Output gain",
198329
+ labelKey: "provider-reolink:schema.s_intercom.intercomGain.label",
196707
198330
  description: "Multiplier applied to PCM samples before IMA-ADPCM encode. 1.0 = neutral, 2.0 ≈ +6dB, 0.5 ≈ -6dB. Hard-clipped at int16 bounds.",
198331
+ descriptionKey: "provider-reolink:schema.s_intercom.intercomGain.description",
196708
198332
  min: .1,
196709
198333
  max: 10,
196710
198334
  step: .1,
@@ -196719,12 +198343,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196719
198343
  id: "keyframe-gop",
196720
198344
  tab: "advanced",
196721
198345
  title: "Keyframe interval (GOP)",
198346
+ titleKey: "provider-reolink:schema.s_keyframe-gop.title",
196722
198347
  description: "Main-stream keyframe interval in seconds. Pushed via `SetEnc` (cmd_id 57) as `<gop><cur>`. Lower = more keyframes (faster seek/recovery, higher bitrate); higher = fewer keyframes (lower bitrate). The camera clamps to its supported range.",
198348
+ descriptionKey: "provider-reolink:schema.s_keyframe-gop.description",
196723
198349
  columns: 1,
196724
198350
  fields: [{
196725
198351
  type: "slider",
196726
198352
  key: "gop",
196727
198353
  label: "Keyframe interval (s)",
198354
+ labelKey: "provider-reolink:schema.s_keyframe-gop.gop.label",
196728
198355
  min: 1,
196729
198356
  max: 10,
196730
198357
  step: 1,
@@ -196737,12 +198364,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196737
198364
  id: "pir-reduce-alarm",
196738
198365
  tab: "advanced",
196739
198366
  title: "PIR detection",
198367
+ titleKey: "provider-reolink:schema.s_pir-reduce-alarm.title",
196740
198368
  description: "Passive-infrared false-alarm filter. Pushed via `SetPirInfo` (cmd_id 213) as `reduceAlarm`. Enabling makes the camera require a stronger/longer PIR signal before triggering, cutting false motion alerts from heat/light flicker.",
198369
+ descriptionKey: "provider-reolink:schema.s_pir-reduce-alarm.description",
196741
198370
  columns: 1,
196742
198371
  fields: [{
196743
198372
  type: "boolean",
196744
198373
  key: "pirReduceFalseAlarm",
196745
198374
  label: "Reduce false alarms",
198375
+ labelKey: "provider-reolink:schema.s_pir-reduce-alarm.pirReduceFalseAlarm.label",
196746
198376
  default: cache.pirSnapshot.reduceFalseAlarm ?? false,
196747
198377
  style: "switch"
196748
198378
  }]
@@ -196751,13 +198381,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196751
198381
  id: "auto-reboot",
196752
198382
  tab: "advanced",
196753
198383
  title: "Auto reboot",
198384
+ titleKey: "provider-reolink:schema.s_auto-reboot.title",
196754
198385
  description: "Scheduled automatic reboot. Pushed via `SetAutoReboot` (cmd_id 100). Useful for long-uptime stability. Choose a day (or every day) + time; the camera reboots once per matching window.",
198386
+ descriptionKey: "provider-reolink:schema.s_auto-reboot.description",
196755
198387
  columns: 2,
196756
198388
  fields: [
196757
198389
  {
196758
198390
  type: "boolean",
196759
198391
  key: "autoRebootEnabled",
196760
198392
  label: "Auto reboot",
198393
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootEnabled.label",
196761
198394
  default: cache.autoRebootSnapshot.enabled ?? false,
196762
198395
  style: "switch",
196763
198396
  span: 2
@@ -196766,40 +198399,49 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196766
198399
  type: "select",
196767
198400
  key: "autoRebootWeekDay",
196768
198401
  label: "Day",
198402
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.label",
196769
198403
  default: cache.autoRebootSnapshot.weekDay ?? "everyday",
196770
198404
  span: 2,
196771
198405
  options: [
196772
198406
  {
196773
198407
  value: "everyday",
196774
- label: "Every day"
198408
+ label: "Every day",
198409
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_everyday.label"
196775
198410
  },
196776
198411
  {
196777
198412
  value: "Sunday",
196778
- label: "Sunday"
198413
+ label: "Sunday",
198414
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Sunday.label"
196779
198415
  },
196780
198416
  {
196781
198417
  value: "Monday",
196782
- label: "Monday"
198418
+ label: "Monday",
198419
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Monday.label"
196783
198420
  },
196784
198421
  {
196785
198422
  value: "Tuesday",
196786
- label: "Tuesday"
198423
+ label: "Tuesday",
198424
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Tuesday.label"
196787
198425
  },
196788
198426
  {
196789
198427
  value: "Wednesday",
196790
- label: "Wednesday"
198428
+ label: "Wednesday",
198429
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Wednesday.label"
196791
198430
  },
196792
198431
  {
196793
198432
  value: "Thursday",
196794
- label: "Thursday"
198433
+ label: "Thursday",
198434
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Thursday.label"
196795
198435
  },
196796
198436
  {
196797
198437
  value: "Friday",
196798
- label: "Friday"
198438
+ label: "Friday",
198439
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Friday.label"
196799
198440
  },
196800
198441
  {
196801
198442
  value: "Saturday",
196802
- label: "Saturday"
198443
+ label: "Saturday",
198444
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Saturday.label"
196803
198445
  }
196804
198446
  ]
196805
198447
  },
@@ -196807,6 +198449,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196807
198449
  type: "number",
196808
198450
  key: "autoRebootHour",
196809
198451
  label: "Hour (0-23)",
198452
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootHour.label",
196810
198453
  default: cache.autoRebootSnapshot.hour ?? 3,
196811
198454
  min: 0,
196812
198455
  max: 23,
@@ -196816,6 +198459,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196816
198459
  type: "number",
196817
198460
  key: "autoRebootMinute",
196818
198461
  label: "Minute (0-59)",
198462
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootMinute.label",
196819
198463
  default: cache.autoRebootSnapshot.minute ?? 0,
196820
198464
  min: 0,
196821
198465
  max: 59,
@@ -196827,41 +198471,52 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196827
198471
  id: "debug",
196828
198472
  tab: "advanced",
196829
198473
  title: "Debug",
198474
+ titleKey: "provider-reolink:schema.s_debug.title",
196830
198475
  description: "Lib-side Baichuan tracing. Changing these resets the control-channel client (active streams keep running) so new debug options take effect on the next dial.",
198476
+ descriptionKey: "provider-reolink:schema.s_debug.description",
196831
198477
  columns: 1,
196832
198478
  fields: [{
196833
198479
  type: "boolean",
196834
198480
  key: "debugGeneral",
196835
198481
  label: "General debug logs",
198482
+ labelKey: "provider-reolink:schema.s_debug.debugGeneral.label",
196836
198483
  description: "Forwards `DebugOptions.general=true` — verbose lib protocol logs.",
198484
+ descriptionKey: "provider-reolink:schema.s_debug.debugGeneral.description",
196837
198485
  default: false,
196838
198486
  style: "switch"
196839
198487
  }, {
196840
198488
  type: "multiselect",
196841
198489
  key: "debugSocketLogs",
196842
198490
  label: "Socket trace categories",
198491
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.label",
196843
198492
  description: "Per-area lib tracing. Pick only what you need; each adds log volume.",
198493
+ descriptionKey: "provider-reolink:schema.s_debug.debugSocketLogs.description",
196844
198494
  default: [],
196845
198495
  options: [
196846
198496
  {
196847
198497
  value: "debugRtsp",
196848
- label: "RTSP (proxy/server traffic)"
198498
+ label: "RTSP (proxy/server traffic)",
198499
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_debugRtsp.label"
196849
198500
  },
196850
198501
  {
196851
198502
  value: "traceNativeStream",
196852
- label: "Native stream (cmd 3/4 + H.264/H.265 + param sets)"
198503
+ label: "Native stream (cmd 3/4 + H.264/H.265 + param sets)",
198504
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_traceNativeStream.label"
196853
198505
  },
196854
198506
  {
196855
198507
  value: "traceRecordings",
196856
- label: "Recordings (FileInfoList, findAlarmVideo, download)"
198508
+ label: "Recordings (FileInfoList, findAlarmVideo, download)",
198509
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_traceRecordings.label"
196857
198510
  },
196858
198511
  {
196859
198512
  value: "traceTalk",
196860
- label: "Talkback (cmd 10/11/201/202)"
198513
+ label: "Talkback (cmd 10/11/201/202)",
198514
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_traceTalk.label"
196861
198515
  },
196862
198516
  {
196863
198517
  value: "traceEvents",
196864
- label: "Events (cmd 33 — AlarmEventList push)"
198518
+ label: "Events (cmd 33 — AlarmEventList push)",
198519
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_traceEvents.label"
196865
198520
  }
196866
198521
  ]
196867
198522
  }]
@@ -198336,13 +199991,16 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198336
199991
  id: "connection",
198337
199992
  tab: "general",
198338
199993
  title: "Connection",
199994
+ titleKey: "provider-reolink:hub.s_connection.title",
198339
199995
  description: "Baichuan protocol connection settings for the hub. The same socket fronts every adopted channel — child cameras inherit these credentials, no per-channel login needed.",
199996
+ descriptionKey: "provider-reolink:hub.s_connection.description",
198340
199997
  columns: 2,
198341
199998
  fields: [
198342
199999
  {
198343
200000
  type: "text",
198344
200001
  key: "host",
198345
200002
  label: "Hub IP",
200003
+ labelKey: "provider-reolink:hub.s_connection.host.label",
198346
200004
  required: true,
198347
200005
  placeholder: "192.168.10.100"
198348
200006
  },
@@ -198350,6 +200008,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198350
200008
  type: "number",
198351
200009
  key: "port",
198352
200010
  label: "Port",
200011
+ labelKey: "provider-reolink:hub.s_connection.port.label",
198353
200012
  default: 9e3,
198354
200013
  min: 1,
198355
200014
  max: 65535,
@@ -198359,12 +200018,14 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198359
200018
  type: "text",
198360
200019
  key: "username",
198361
200020
  label: "Username",
200021
+ labelKey: "provider-reolink:hub.s_connection.username.label",
198362
200022
  default: "admin"
198363
200023
  },
198364
200024
  {
198365
200025
  type: "password",
198366
200026
  key: "password",
198367
200027
  label: "Password",
200028
+ labelKey: "provider-reolink:hub.s_connection.password.label",
198368
200029
  required: true,
198369
200030
  showToggle: true
198370
200031
  }
@@ -198374,18 +200035,22 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198374
200035
  id: "identity",
198375
200036
  tab: "general",
198376
200037
  title: "Hub identity",
200038
+ titleKey: "provider-reolink:hub.s_identity.title",
198377
200039
  description: "Read-only summary from the autodetect probe. Channel count and model are persisted at first contact so the panel renders without re-probing.",
200040
+ descriptionKey: "provider-reolink:hub.s_identity.description",
198378
200041
  columns: 2,
198379
200042
  fields: [{
198380
200043
  type: "text",
198381
200044
  key: "_model",
198382
200045
  label: "Model",
200046
+ labelKey: "provider-reolink:hub.s_identity._model.label",
198383
200047
  default: cache?.model ?? "unknown",
198384
200048
  disabled: true
198385
200049
  }, {
198386
200050
  type: "text",
198387
200051
  key: "_channelCount",
198388
200052
  label: "Total channels",
200053
+ labelKey: "provider-reolink:hub.s_identity._channelCount.label",
198389
200054
  default: String(cache?.channelCount ?? 0),
198390
200055
  disabled: true
198391
200056
  }]
@@ -198394,41 +200059,52 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198394
200059
  id: "debug",
198395
200060
  tab: "advanced",
198396
200061
  title: "Debug",
200062
+ titleKey: "provider-reolink:hub.s_debug.title",
198397
200063
  description: "Lib-side Baichuan tracing. Changing these resets the control-channel client (active streams keep running) so new debug options take effect on the next dial.",
200064
+ descriptionKey: "provider-reolink:hub.s_debug.description",
198398
200065
  columns: 1,
198399
200066
  fields: [{
198400
200067
  type: "boolean",
198401
200068
  key: "debugGeneral",
198402
200069
  label: "General debug logs",
200070
+ labelKey: "provider-reolink:hub.s_debug.debugGeneral.label",
198403
200071
  description: "Forwards `DebugOptions.general=true` — verbose lib protocol logs.",
200072
+ descriptionKey: "provider-reolink:hub.s_debug.debugGeneral.description",
198404
200073
  default: false,
198405
200074
  style: "switch"
198406
200075
  }, {
198407
200076
  type: "multiselect",
198408
200077
  key: "debugSocketLogs",
198409
200078
  label: "Socket trace categories",
200079
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.label",
198410
200080
  description: "Per-area lib tracing. Pick only what you need; each adds log volume.",
200081
+ descriptionKey: "provider-reolink:hub.s_debug.debugSocketLogs.description",
198411
200082
  default: [],
198412
200083
  options: [
198413
200084
  {
198414
200085
  value: "debugRtsp",
198415
- label: "RTSP (proxy/server traffic)"
200086
+ label: "RTSP (proxy/server traffic)",
200087
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_debugRtsp.label"
198416
200088
  },
198417
200089
  {
198418
200090
  value: "traceNativeStream",
198419
- label: "Native stream (cmd 3/4 + H.264/H.265 + param sets)"
200091
+ label: "Native stream (cmd 3/4 + H.264/H.265 + param sets)",
200092
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_traceNativeStream.label"
198420
200093
  },
198421
200094
  {
198422
200095
  value: "traceRecordings",
198423
- label: "Recordings (FileInfoList, findAlarmVideo, download)"
200096
+ label: "Recordings (FileInfoList, findAlarmVideo, download)",
200097
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_traceRecordings.label"
198424
200098
  },
198425
200099
  {
198426
200100
  value: "traceTalk",
198427
- label: "Talkback (cmd 10/11/201/202)"
200101
+ label: "Talkback (cmd 10/11/201/202)",
200102
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_traceTalk.label"
198428
200103
  },
198429
200104
  {
198430
200105
  value: "traceEvents",
198431
- label: "Events (cmd 33 — AlarmEventList push)"
200106
+ label: "Events (cmd 33 — AlarmEventList push)",
200107
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_traceEvents.label"
198432
200108
  }
198433
200109
  ]
198434
200110
  }]
@@ -199010,8 +200686,8 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
199010
200686
  this.api = null;
199011
200687
  }
199012
200688
  const cfg = this.config.values;
199013
- if (!cfg.host) throw new Error("Reolink Hub: host not configured");
199014
- if (!cfg.password) throw new Error("Reolink Hub: password not configured");
200689
+ if (!cfg.host) throw userError("reolink.hub-host-missing", void 0, "Reolink Hub: host not configured");
200690
+ if (!cfg.password) throw userError("reolink.hub-password-missing", void 0, "Reolink Hub: password not configured");
199015
200691
  const api = new ReolinkBaichuanApi({
199016
200692
  host: cfg.host,
199017
200693
  port: cfg.port,
@@ -199163,31 +200839,38 @@ function buildDiscoveryParamsFormSchema() {
199163
200839
  return { sections: [{
199164
200840
  id: "credentials",
199165
200841
  title: "Credentials",
200842
+ titleKey: "provider-reolink:discoveryParams.s_credentials.title",
199166
200843
  description: "Used to probe the cameras and pre-filled into each result so you can add it in one click. Assumes all cameras share these credentials.",
200844
+ descriptionKey: "provider-reolink:discoveryParams.s_credentials.description",
199167
200845
  columns: 2,
199168
200846
  fields: [{
199169
200847
  type: "text",
199170
200848
  key: "username",
199171
200849
  label: "Username",
200850
+ labelKey: "provider-reolink:discoveryParams.s_credentials.username.label",
199172
200851
  default: "admin",
199173
200852
  required: true
199174
200853
  }, {
199175
200854
  type: "password",
199176
200855
  key: "password",
199177
200856
  label: "Password",
200857
+ labelKey: "provider-reolink:discoveryParams.s_credentials.password.label",
199178
200858
  required: true,
199179
200859
  showToggle: true
199180
200860
  }]
199181
200861
  }, {
199182
200862
  id: "scan",
199183
200863
  title: "Scan options",
200864
+ titleKey: "provider-reolink:discoveryParams.s_scan.title",
199184
200865
  description: "Leave the network blank to scan the local subnet. To find cameras on a different subnet, enter its CIDR (e.g. 192.168.20.0/24).",
200866
+ descriptionKey: "provider-reolink:discoveryParams.s_scan.description",
199185
200867
  columns: 1,
199186
200868
  fields: [
199187
200869
  {
199188
200870
  type: "text",
199189
200871
  key: "networkCidr",
199190
200872
  label: "Network (CIDR)",
200873
+ labelKey: "provider-reolink:discoveryParams.s_scan.networkCidr.label",
199191
200874
  required: false,
199192
200875
  placeholder: "192.168.20.0/24"
199193
200876
  },
@@ -199195,12 +200878,14 @@ function buildDiscoveryParamsFormSchema() {
199195
200878
  type: "boolean",
199196
200879
  key: "enableOnvif",
199197
200880
  label: "Include ONVIF discovery",
200881
+ labelKey: "provider-reolink:discoveryParams.s_scan.enableOnvif.label",
199198
200882
  default: true
199199
200883
  },
199200
200884
  {
199201
200885
  type: "number",
199202
200886
  key: "timeoutMs",
199203
200887
  label: "Scan timeout (ms)",
200888
+ labelKey: "provider-reolink:discoveryParams.s_scan.timeoutMs.label",
199204
200889
  min: 1e3,
199205
200890
  max: 3e4,
199206
200891
  default: 5e3
@@ -199213,11 +200898,13 @@ function buildCreationFormSchema() {
199213
200898
  {
199214
200899
  id: "identity",
199215
200900
  title: "Camera",
200901
+ titleKey: "provider-reolink:creation.s_identity.title",
199216
200902
  columns: 1,
199217
200903
  fields: [{
199218
200904
  type: "text",
199219
200905
  key: "name",
199220
200906
  label: "Name",
200907
+ labelKey: "provider-reolink:creation.s_identity.name.label",
199221
200908
  required: true,
199222
200909
  placeholder: "Front Door"
199223
200910
  }]
@@ -199225,18 +200912,22 @@ function buildCreationFormSchema() {
199225
200912
  {
199226
200913
  id: "credentials",
199227
200914
  title: "Credentials",
200915
+ titleKey: "provider-reolink:creation.s_credentials.title",
199228
200916
  description: "Required for the autodetect probe — entered before the host so Test can run immediately.",
200917
+ descriptionKey: "provider-reolink:creation.s_credentials.description",
199229
200918
  columns: 2,
199230
200919
  fields: [{
199231
200920
  type: "text",
199232
200921
  key: "username",
199233
200922
  label: "Username",
200923
+ labelKey: "provider-reolink:creation.s_credentials.username.label",
199234
200924
  default: "admin",
199235
200925
  required: true
199236
200926
  }, {
199237
200927
  type: "password",
199238
200928
  key: "password",
199239
200929
  label: "Password",
200930
+ labelKey: "provider-reolink:creation.s_credentials.password.label",
199240
200931
  required: true,
199241
200932
  showToggle: true
199242
200933
  }]
@@ -199244,27 +200935,34 @@ function buildCreationFormSchema() {
199244
200935
  {
199245
200936
  id: "connection",
199246
200937
  title: "Connection",
200938
+ titleKey: "provider-reolink:creation.s_connection.title",
199247
200939
  description: "Click Test to auto-detect the device type (regular camera / NVR / battery / UDP-only). The detected type is then used at save time.",
200940
+ descriptionKey: "provider-reolink:creation.s_connection.description",
199248
200941
  columns: 2,
199249
200942
  fields: [
199250
200943
  {
199251
200944
  type: "select",
199252
200945
  key: "transport",
199253
200946
  label: "Connection mode",
200947
+ labelKey: "provider-reolink:creation.s_connection.transport.label",
199254
200948
  default: "auto",
199255
200949
  description: "Auto: try TCP first, fall back to UDP — best for unknown devices. UDP is required for battery cameras behind NAT.",
200950
+ descriptionKey: "provider-reolink:creation.s_connection.transport.description",
199256
200951
  options: [
199257
200952
  {
199258
200953
  value: "auto",
199259
- label: "Auto (recommended)"
200954
+ label: "Auto (recommended)",
200955
+ labelKey: "provider-reolink:creation.s_connection.transport.o_auto.label"
199260
200956
  },
199261
200957
  {
199262
200958
  value: "tcp",
199263
- label: "TCP only"
200959
+ label: "TCP only",
200960
+ labelKey: "provider-reolink:creation.s_connection.transport.o_tcp.label"
199264
200961
  },
199265
200962
  {
199266
200963
  value: "udp",
199267
- label: "UDP only (battery / cloud)"
200964
+ label: "UDP only (battery / cloud)",
200965
+ labelKey: "provider-reolink:creation.s_connection.transport.o_udp.label"
199268
200966
  }
199269
200967
  ]
199270
200968
  },
@@ -199272,20 +200970,24 @@ function buildCreationFormSchema() {
199272
200970
  type: "probe",
199273
200971
  key: "host",
199274
200972
  label: "Host (IP / hostname / UID)",
200973
+ labelKey: "provider-reolink:creation.s_connection.host.label",
199275
200974
  required: true,
199276
200975
  placeholder: "192.168.10.100",
199277
200976
  description: "For UDP-only mode you can leave the host blank and supply the camera UID below — the library will discover it on the network.",
200977
+ descriptionKey: "provider-reolink:creation.s_connection.host.description",
199278
200978
  inputType: "text"
199279
200979
  },
199280
200980
  {
199281
200981
  type: "number",
199282
200982
  key: "port",
199283
200983
  label: "Port",
200984
+ labelKey: "provider-reolink:creation.s_connection.port.label",
199284
200985
  default: 9e3,
199285
200986
  min: 1,
199286
200987
  max: 65535,
199287
200988
  step: 1,
199288
200989
  description: "Baichuan port. Default 9000 — applies to TCP. Ignored for UDP transport.",
200990
+ descriptionKey: "provider-reolink:creation.s_connection.port.description",
199289
200991
  showWhen: {
199290
200992
  field: "transport",
199291
200993
  notEquals: "udp"
@@ -199295,8 +200997,10 @@ function buildCreationFormSchema() {
199295
200997
  type: "text",
199296
200998
  key: "uid",
199297
200999
  label: "Device UID",
201000
+ labelKey: "provider-reolink:creation.s_connection.uid.label",
199298
201001
  placeholder: "9527000XXXXXXXX",
199299
201002
  description: "Required for battery / UDP cameras when not on the same LAN. Optional for local-direct UDP discovery and ignored for pure TCP.",
201003
+ descriptionKey: "provider-reolink:creation.s_connection.uid.description",
199300
201004
  showWhen: {
199301
201005
  field: "transport",
199302
201006
  notEquals: "tcp"
@@ -199835,25 +201539,30 @@ function buildEmailPushSettingsSchema(recommendedHost) {
199835
201539
  return { sections: [{
199836
201540
  id: "email-push",
199837
201541
  title: "Battery-cam Motion (Email/SMTP Push)",
201542
+ titleKey: "provider-reolink:emailPushSettings.s_email-push.title",
199838
201543
  description: "Emulate an SMTP server that battery Reolink cameras push to on motion. Lets you capture motion from sleeping battery cams without polling them awake. Enable, then use \"Auto-configure email push\" on each battery camera.",
201544
+ descriptionKey: "provider-reolink:emailPushSettings.s_email-push.description",
199839
201545
  columns: 2,
199840
201546
  fields: [
199841
201547
  {
199842
201548
  type: "boolean",
199843
201549
  key: "emailPushEnabled",
199844
201550
  label: "Enable Email Push Server",
201551
+ labelKey: "provider-reolink:emailPushSettings.s_email-push.emailPushEnabled.label",
199845
201552
  default: true
199846
201553
  },
199847
201554
  {
199848
201555
  type: "info",
199849
201556
  key: "emailPushRecommendedHost",
199850
201557
  label: "Camera-facing host",
201558
+ labelKey: "provider-reolink:emailPushSettings.s_email-push.emailPushRecommendedHost.label",
199851
201559
  content: `Point cameras at this host: ${recommendedHost}`
199852
201560
  },
199853
201561
  {
199854
201562
  type: "number",
199855
201563
  key: "emailPushPort",
199856
201564
  label: "SMTP Port",
201565
+ labelKey: "provider-reolink:emailPushSettings.s_email-push.emailPushPort.label",
199857
201566
  default: DEFAULT_EMAIL_PUSH_PORT,
199858
201567
  min: 1,
199859
201568
  max: 65535
@@ -199862,6 +201571,7 @@ function buildEmailPushSettingsSchema(recommendedHost) {
199862
201571
  type: "text",
199863
201572
  key: "emailPushDomain",
199864
201573
  label: "Virtual Domain",
201574
+ labelKey: "provider-reolink:emailPushSettings.s_email-push.emailPushDomain.label",
199865
201575
  default: DEFAULT_EMAIL_PUSH_DOMAIN,
199866
201576
  placeholder: DEFAULT_EMAIL_PUSH_DOMAIN
199867
201577
  }
@@ -199869,24 +201579,29 @@ function buildEmailPushSettingsSchema(recommendedHost) {
199869
201579
  }, {
199870
201580
  id: "email-push-auth",
199871
201581
  title: "Email Push Authentication",
201582
+ titleKey: "provider-reolink:emailPushSettings.s_email-push-auth.title",
199872
201583
  description: "AUTH credentials the camera must present. Reolink firmwares are more reliable with AUTH on. Random values are generated on first boot — re-run Auto-configure on cameras after changing them.",
201584
+ descriptionKey: "provider-reolink:emailPushSettings.s_email-push-auth.description",
199873
201585
  columns: 2,
199874
201586
  fields: [
199875
201587
  {
199876
201588
  type: "boolean",
199877
201589
  key: "emailPushRequireAuth",
199878
201590
  label: "Require AUTH",
201591
+ labelKey: "provider-reolink:emailPushSettings.s_email-push-auth.emailPushRequireAuth.label",
199879
201592
  default: true
199880
201593
  },
199881
201594
  {
199882
201595
  type: "text",
199883
201596
  key: "emailPushAuthUsername",
199884
- label: "Auth Username"
201597
+ label: "Auth Username",
201598
+ labelKey: "provider-reolink:emailPushSettings.s_email-push-auth.emailPushAuthUsername.label"
199885
201599
  },
199886
201600
  {
199887
201601
  type: "password",
199888
201602
  key: "emailPushAuthPassword",
199889
201603
  label: "Auth Password",
201604
+ labelKey: "provider-reolink:emailPushSettings.s_email-push-auth.emailPushAuthPassword.label",
199890
201605
  showToggle: true
199891
201606
  }
199892
201607
  ]
@@ -200073,25 +201788,30 @@ function buildBaichuanWebhookSettingsSchema(recommendedHost) {
200073
201788
  return { sections: [{
200074
201789
  id: "baichuan-webhook",
200075
201790
  title: "Battery-cam Motion (HaCfg Webhook)",
201791
+ titleKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.title",
200076
201792
  description: "HTTP intake for Baichuan HaCfg wake/sleep push (cmd 806/807). Prefer this over SMTP when the camera firmware supports it — lower latency, no AUTH dance. Enable, then use \"Auto-configure HaCfg webhook\" on each battery camera (or set preference to Auto).",
201793
+ descriptionKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.description",
200077
201794
  columns: 2,
200078
201795
  fields: [
200079
201796
  {
200080
201797
  type: "boolean",
200081
201798
  key: "baichuanWebhookEnabled",
200082
201799
  label: "Enable HaCfg Webhook Server",
201800
+ labelKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookEnabled.label",
200083
201801
  default: true
200084
201802
  },
200085
201803
  {
200086
201804
  type: "info",
200087
201805
  key: "baichuanWebhookRecommendedHost",
200088
201806
  label: "Camera-facing host",
201807
+ labelKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookRecommendedHost.label",
200089
201808
  content: `Point cameras at this host: ${recommendedHost}`
200090
201809
  },
200091
201810
  {
200092
201811
  type: "number",
200093
201812
  key: "baichuanWebhookPort",
200094
201813
  label: "HTTP Port",
201814
+ labelKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookPort.label",
200095
201815
  default: DEFAULT_BAICHUAN_WEBHOOK_PORT,
200096
201816
  min: 1,
200097
201817
  max: 65535
@@ -200100,8 +201820,10 @@ function buildBaichuanWebhookSettingsSchema(recommendedHost) {
200100
201820
  type: "text",
200101
201821
  key: "baichuanWebhookBindHost",
200102
201822
  label: "Bind host",
201823
+ labelKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookBindHost.label",
200103
201824
  default: "0.0.0.0",
200104
- description: "0.0.0.0 accepts on every interface (cameras on LAN)."
201825
+ description: "0.0.0.0 accepts on every interface (cameras on LAN).",
201826
+ descriptionKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookBindHost.description"
200105
201827
  }
200106
201828
  ]
200107
201829
  }] };
@@ -200657,7 +202379,7 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
200657
202379
  },
200658
202380
  wake: async (deviceId) => {
200659
202381
  const device = this.ctx.kernel.deviceRegistry?.getById(deviceId);
200660
- if (!(device instanceof ReolinkCamera)) throw new Error(`clip refresh: device ${String(deviceId)} is not a Reolink camera here`);
202382
+ if (!(device instanceof ReolinkCamera)) throw userError("reolink.clip-device-not-reolink", { deviceId: String(deviceId) }, `clip refresh: device ${String(deviceId)} is not a Reolink camera here`);
200661
202383
  await device.wakeForClipCatalogRefresh();
200662
202384
  },
200663
202385
  prefetchStills: async (deviceId) => service.prefetchThumbnails(deviceId),