@camstack/addon-provider-reolink 1.2.47 → 1.2.49

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.
Files changed (3) hide show
  1. package/dist/addon.js +258 -17
  2. package/dist/addon.mjs +258 -17
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -26,7 +26,7 @@ let fs_promises = require("fs/promises");
26
26
  fs_promises = require_chunk.__toESM(fs_promises, 1);
27
27
  let node_os = require("node:os");
28
28
  node_os = require_chunk.__toESM(node_os);
29
- //#region ../types/dist/event-category-XfKNtfCc.mjs
29
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
30
30
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
31
31
  EventCategory["SystemBoot"] = "system.boot";
32
32
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -42,6 +42,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
42
42
  */
43
43
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
44
44
  /**
45
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
46
+ * Emitted only when the material on disk actually changed, so an
47
+ * operator who trusted the old certificate by hand is told rather than
48
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
49
+ *
50
+ * Rule: docs/decisions/adr-0227-*.md
51
+ */
52
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
53
+ /**
45
54
  * A newer addon or server-root package version was found by the
46
55
  * authoritative registry check. Emitted once when any observed
47
56
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8225,6 +8234,15 @@ var LabelDefinitionSchema = object({
8225
8234
  description: string().optional(),
8226
8235
  icon: string().optional()
8227
8236
  });
8237
+ var ClassMapDefinitionSchema = object({
8238
+ mapping: record(string(), _enum([
8239
+ "person",
8240
+ "vehicle",
8241
+ "animal",
8242
+ "package"
8243
+ ])),
8244
+ preserveOriginal: boolean()
8245
+ });
8228
8246
  var MODEL_FORMATS = [
8229
8247
  "onnx",
8230
8248
  "coreml",
@@ -8403,7 +8421,13 @@ var ModelCatalogEntrySchema = object({
8403
8421
  * `id` stays the source of truth for resolution/download/persistence; grouping
8404
8422
  * is a presentation overlay resolved back to an `id`.
8405
8423
  */
8406
- group: ModelVariantGroupSchema.optional()
8424
+ group: ModelVariantGroupSchema.optional(),
8425
+ /**
8426
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8427
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8428
+ * labels already ARE the CamStack macros (Scrypted identity map).
8429
+ */
8430
+ classMap: ClassMapDefinitionSchema.optional()
8407
8431
  });
8408
8432
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8409
8433
  format: literal("openvino"),
@@ -8432,7 +8456,8 @@ var ModelConvertMetadataSchema = object({
8432
8456
  "ocr",
8433
8457
  "segmentation"
8434
8458
  ]),
8435
- faceAlignment: boolean().optional()
8459
+ faceAlignment: boolean().optional(),
8460
+ classMap: ClassMapDefinitionSchema.optional()
8436
8461
  });
8437
8462
  var ConvertResultSchema = object({
8438
8463
  entry: ModelCatalogEntrySchema,
@@ -17737,6 +17762,33 @@ var NativeCropRefSchema = object({
17737
17762
  h: number()
17738
17763
  })
17739
17764
  });
17765
+ object({
17766
+ crop: object({
17767
+ left: number(),
17768
+ top: number(),
17769
+ width: number().positive(),
17770
+ height: number().positive()
17771
+ }).optional(),
17772
+ content: object({
17773
+ width: number().int().positive(),
17774
+ height: number().int().positive()
17775
+ }),
17776
+ fit: _enum(["stretch", "contain"]),
17777
+ format: _enum([
17778
+ "rgb",
17779
+ "gray",
17780
+ "jpeg"
17781
+ ])
17782
+ });
17783
+ var FrameRefSchema = object({
17784
+ registryId: string().min(1),
17785
+ id: string().min(1),
17786
+ width: number().int().positive(),
17787
+ height: number().int().positive(),
17788
+ format: _enum(["rgb", "gray"]),
17789
+ timestamp: number(),
17790
+ capturedAt: number().optional()
17791
+ });
17740
17792
  var ModelFormatSchema$1 = _enum([
17741
17793
  "onnx",
17742
17794
  "coreml",
@@ -17981,6 +18033,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17981
18033
  steps: array(PipelineStepInputSchema).min(1),
17982
18034
  frame: FrameInputSchema.optional(),
17983
18035
  /**
18036
+ * Process-local lazy frame. Valid only when caller and provider resolve
18037
+ * in the same execution-group process; split/cross-node callers use
18038
+ * `frame`/`image` inline compatibility instead.
18039
+ */
18040
+ frameRef: FrameRefSchema.optional(),
18041
+ /**
17984
18042
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17985
18043
  * the decoded pixels live in. One more member of the one-of
17986
18044
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18276,7 +18334,10 @@ var NativeCropResultSchema = object({
18276
18334
  * Which source served this crop, so a quality-sensitive consumer (the native
18277
18335
  * `keyFrame`) can reject a degraded fallback:
18278
18336
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18279
- * quality path).
18337
+ * quality path). A subject-tile serve is also native-resolution and stays
18338
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18339
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18340
+ * internal crop result (`nativeHits` vs `tileHits`).
18280
18341
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18281
18342
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18282
18343
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18767,12 +18828,41 @@ var RunnerLocalLoadSchema = object({
18767
18828
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18768
18829
  * working unchanged when they switch to reading from the runner cap.
18769
18830
  */
18831
+ var FrameLazyCountersSchema = object({
18832
+ framesDecoded: number(),
18833
+ framesAdmitted: number(),
18834
+ framesDroppedPixelFree: number(),
18835
+ viewsMaterialized: number(),
18836
+ viewsSkipped: number(),
18837
+ workerToRunnerBytes: number(),
18838
+ runnerToPoolRawBytes: number(),
18839
+ runnerToPoolJpegBytes: number(),
18840
+ onDemandFullFrameRequests: number(),
18841
+ onDemandCropRequests: number(),
18842
+ nativeHits: number(),
18843
+ nativeMisses: number(),
18844
+ tileHits: number(),
18845
+ tileMisses: number(),
18846
+ fallbackHits: number(),
18847
+ fallbackMisses: number(),
18848
+ retainedWritesAvoided: number(),
18849
+ residentRefs: number(),
18850
+ residentBytes: number(),
18851
+ releases: number(),
18852
+ evictions: number(),
18853
+ staleMisses: number()
18854
+ });
18855
+ var FrameLazyMetricsSchema = object({
18856
+ node: FrameLazyCountersSchema,
18857
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18858
+ });
18770
18859
  var RunnerLocalMetricsSchema = object({
18771
18860
  nodeId: string(),
18772
18861
  activeCameras: number(),
18773
18862
  throttledCameras: number(),
18774
18863
  avgInferenceTimeMs: number(),
18775
- queueDepth: number()
18864
+ queueDepth: number(),
18865
+ frameLazy: FrameLazyMetricsSchema.optional()
18776
18866
  });
18777
18867
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18778
18868
  handle: FrameHandleSchema,
@@ -20176,6 +20266,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
20176
20266
  location: StorageLocationSchema,
20177
20267
  relativePath: string()
20178
20268
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
20269
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20270
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20271
+ var ProfileSettingsBagSchema = record(string(), unknown());
20179
20272
  /**
20180
20273
  * A live terminal session hosted by the provider addon. Output and input do
20181
20274
  * NOT flow through the capability — they use the addon data plane
@@ -20205,7 +20298,14 @@ var TerminalSessionInfoSchema = object({
20205
20298
  var TerminalProfileInfoSchema = object({
20206
20299
  profileId: string(),
20207
20300
  label: string(),
20208
- description: string().optional()
20301
+ description: string().optional(),
20302
+ /** Spawn defaults the instance form copies on create. */
20303
+ executable: string().optional(),
20304
+ args: array(string()).readonly().optional(),
20305
+ cwd: string().optional(),
20306
+ environment: array(string()).readonly().optional(),
20307
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20308
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
20209
20309
  });
20210
20310
  /**
20211
20311
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20218,7 +20318,12 @@ var TerminalInstanceInfoSchema = object({
20218
20318
  profileId: string(),
20219
20319
  profileLabel: string(),
20220
20320
  name: string(),
20221
- enabled: boolean()
20321
+ enabled: boolean(),
20322
+ executable: string(),
20323
+ args: array(string()).readonly(),
20324
+ cwd: string(),
20325
+ environment: array(string()).readonly(),
20326
+ profileSettings: ProfileSettingsBagSchema
20222
20327
  });
20223
20328
  var TerminalLegacyCameraSchema = object({
20224
20329
  stableId: string(),
@@ -20248,7 +20353,23 @@ var TerminalOutputBatchSchema = object({
20248
20353
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
20249
20354
  targetNodeId: string().min(1),
20250
20355
  profileId: string().min(1),
20251
- name: string().trim().min(1).max(160).optional()
20356
+ name: string().trim().min(1).max(160).optional(),
20357
+ executable: string().max(1024).optional(),
20358
+ args: array(string().max(2048)).max(64).optional(),
20359
+ cwd: string().max(1024).optional(),
20360
+ environment: array(string().max(4096)).max(64).optional(),
20361
+ profileSettings: ProfileSettingsBagSchema.optional()
20362
+ }), TerminalInstanceInfoSchema, {
20363
+ kind: "mutation",
20364
+ auth: "admin"
20365
+ }), method(object({
20366
+ instanceId: string().min(1),
20367
+ name: string().trim().min(1).max(160).optional(),
20368
+ executable: string().max(1024).optional(),
20369
+ args: array(string().max(2048)).max(64).optional(),
20370
+ cwd: string().max(1024).optional(),
20371
+ environment: array(string().max(4096)).max(64).optional(),
20372
+ profileSettings: ProfileSettingsBagSchema.optional()
20252
20373
  }), TerminalInstanceInfoSchema, {
20253
20374
  kind: "mutation",
20254
20375
  auth: "admin"
@@ -20270,7 +20391,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20270
20391
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20271
20392
  profileId: string(),
20272
20393
  cols: number().int().positive(),
20273
- rows: number().int().positive()
20394
+ rows: number().int().positive(),
20395
+ executable: string().max(1024).optional(),
20396
+ args: array(string().max(2048)).max(64).optional(),
20397
+ cwd: string().max(1024).optional(),
20398
+ environment: array(string().max(4096)).max(64).optional()
20274
20399
  }), TerminalSessionInfoSchema, {
20275
20400
  kind: "mutation",
20276
20401
  auth: "admin"
@@ -24145,10 +24270,10 @@ var lawnMowerControlCapability = {
24145
24270
  *
24146
24271
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
24147
24272
  * to receive an ordered list of candidate base URLs it should race
24148
- * on connect — LAN IPv4 first (lowest latency when on same network),
24149
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
24150
- * race them with short timeouts and stick with the winner for the
24151
- * session.
24273
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24274
+ * when on the same network), then public hostname (if a tunnel is
24275
+ * up). The SDK can race them with short timeouts and stick with the
24276
+ * winner for the session.
24152
24277
  *
24153
24278
  * Why hub-only: agents are not directly addressable by the operator's
24154
24279
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24303,6 +24428,17 @@ var NotificationEndpointSchema = object({
24303
24428
  /** What the ranking currently resolves to (null when nothing is reachable). */
24304
24429
  resolved: string().nullable()
24305
24430
  });
24431
+ /**
24432
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24433
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24434
+ * currently expands to, so the UI can show the effective set either way.
24435
+ */
24436
+ var ViewerEndpointsSchema = object({
24437
+ /** The operator's explicit race set, or empty for AUTO. */
24438
+ baseUrls: array(string()).readonly(),
24439
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24440
+ resolved: array(string()).readonly()
24441
+ });
24306
24442
  var AllowedAddressesSchema = object({
24307
24443
  /**
24308
24444
  * Allowlist of interface addresses operators have explicitly opted
@@ -24311,6 +24447,20 @@ var AllowedAddressesSchema = object({
24311
24447
  * Network Addresses admin page and persisted by the addon.
24312
24448
  */
24313
24449
  addresses: array(string()).readonly() });
24450
+ var TlsStatusSchema = object({
24451
+ mode: _enum([
24452
+ "generated",
24453
+ "uploaded",
24454
+ "disabled"
24455
+ ]),
24456
+ leafFingerprintSha256: string().nullable(),
24457
+ caFingerprintSha256: string().nullable(),
24458
+ validTo: string().nullable(),
24459
+ sans: array(string()),
24460
+ caCertPem: string().nullable(),
24461
+ reissueError: string().nullable(),
24462
+ restartRequired: boolean()
24463
+ });
24314
24464
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24315
24465
  /**
24316
24466
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24320,17 +24470,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24320
24470
  */
24321
24471
  port: number().int().min(1).max(65535).optional(),
24322
24472
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24323
- * candidate. Default `true`. */
24473
+ * candidate. Default `false` — loopback is not a client route. */
24324
24474
  includeLoopback: boolean().optional(),
24325
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24326
- * Default `false`. */
24475
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24476
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24477
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24327
24478
  ipv4Only: boolean().optional(),
24328
24479
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24329
24480
  * Pass `'https'` when the caller is itself loaded over HTTPS
24330
24481
  * to avoid mixed-content blocks in the browser. The public
24331
24482
  * tunnel always emits `https://` regardless. */
24332
24483
  scheme: _enum(["http", "https"]).optional()
24333
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
24484
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24485
+ kind: "mutation",
24486
+ auth: "admin"
24487
+ }), method(object({
24488
+ certPem: string().min(1),
24489
+ keyPem: string().min(1),
24490
+ caPem: string().optional()
24491
+ }), TlsStatusSchema, {
24492
+ kind: "mutation",
24493
+ auth: "admin"
24494
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24495
+ kind: "mutation",
24496
+ auth: "admin"
24497
+ });
24334
24498
  var LockControlStatusSchema = object({
24335
24499
  /** Lifecycle state of the lock. `jammed` means the motor reported
24336
24500
  * failure to reach the target — operator intervention required. */
@@ -32809,6 +32973,12 @@ Object.freeze({
32809
32973
  addonId: null,
32810
32974
  access: "create"
32811
32975
  },
32976
+ "localNetwork.downloadCa": {
32977
+ capName: "local-network",
32978
+ capScope: "system",
32979
+ addonId: null,
32980
+ access: "view"
32981
+ },
32812
32982
  "localNetwork.getAllowedAddresses": {
32813
32983
  capName: "local-network",
32814
32984
  capScope: "system",
@@ -32833,18 +33003,42 @@ Object.freeze({
32833
33003
  addonId: null,
32834
33004
  access: "view"
32835
33005
  },
33006
+ "localNetwork.getTlsStatus": {
33007
+ capName: "local-network",
33008
+ capScope: "system",
33009
+ addonId: null,
33010
+ access: "view"
33011
+ },
33012
+ "localNetwork.getViewerEndpoints": {
33013
+ capName: "local-network",
33014
+ capScope: "system",
33015
+ addonId: null,
33016
+ access: "view"
33017
+ },
32836
33018
  "localNetwork.list": {
32837
33019
  capName: "local-network",
32838
33020
  capScope: "system",
32839
33021
  addonId: null,
32840
33022
  access: "view"
32841
33023
  },
33024
+ "localNetwork.regenerateCertificate": {
33025
+ capName: "local-network",
33026
+ capScope: "system",
33027
+ addonId: null,
33028
+ access: "create"
33029
+ },
32842
33030
  "localNetwork.resetAllowlistToBestMatch": {
32843
33031
  capName: "local-network",
32844
33032
  capScope: "system",
32845
33033
  addonId: null,
32846
33034
  access: "delete"
32847
33035
  },
33036
+ "localNetwork.revertToGeneratedCertificate": {
33037
+ capName: "local-network",
33038
+ capScope: "system",
33039
+ addonId: null,
33040
+ access: "create"
33041
+ },
32848
33042
  "localNetwork.setAllowedAddresses": {
32849
33043
  capName: "local-network",
32850
33044
  capScope: "system",
@@ -32857,6 +33051,18 @@ Object.freeze({
32857
33051
  addonId: null,
32858
33052
  access: "create"
32859
33053
  },
33054
+ "localNetwork.setViewerEndpoints": {
33055
+ capName: "local-network",
33056
+ capScope: "system",
33057
+ addonId: null,
33058
+ access: "create"
33059
+ },
33060
+ "localNetwork.uploadCertificate": {
33061
+ capName: "local-network",
33062
+ capScope: "system",
33063
+ addonId: null,
33064
+ access: "create"
33065
+ },
32860
33066
  "lockControl.lock": {
32861
33067
  capName: "lock-control",
32862
33068
  capScope: "device",
@@ -35737,6 +35943,12 @@ Object.freeze({
35737
35943
  addonId: null,
35738
35944
  access: "create"
35739
35945
  },
35946
+ "terminalSession.updateInstance": {
35947
+ capName: "terminal-session",
35948
+ capScope: "system",
35949
+ addonId: null,
35950
+ access: "create"
35951
+ },
35740
35952
  "terminalSession.writeInput": {
35741
35953
  capName: "terminal-session",
35742
35954
  capScope: "system",
@@ -38224,6 +38436,35 @@ Object.freeze(Object.fromEntries([{
38224
38436
  }]
38225
38437
  }].map((s) => [s.stepId, s.defaultModelId])));
38226
38438
  string().min(1);
38439
+ var CLUSTER_STEP_SETTING_FIELDS = [{
38440
+ stepId: "face-embedding",
38441
+ key: "minLandmarkFaceSize",
38442
+ label: "Min face size for recognition (detection px)",
38443
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
38444
+ type: "slider",
38445
+ min: 0,
38446
+ max: 64,
38447
+ step: 2,
38448
+ default: 24
38449
+ }];
38450
+ function clusterStepSettingKey(stepId, fieldKey) {
38451
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
38452
+ }
38453
+ var ClusterSettingNumberSchema = number().finite();
38454
+ function readClusterStepSettings(config) {
38455
+ const out = {};
38456
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
38457
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
38458
+ const value = parsed.success ? parsed.data : field.default;
38459
+ const existing = out[field.stepId] ?? {};
38460
+ out[field.stepId] = {
38461
+ ...existing,
38462
+ [field.key]: value
38463
+ };
38464
+ }
38465
+ return out;
38466
+ }
38467
+ readClusterStepSettings({});
38227
38468
  object({
38228
38469
  /**
38229
38470
  * Fraction of the box's own size added on EACH side before cutting.
package/dist/addon.mjs CHANGED
@@ -21,7 +21,7 @@ import netImpl from "net";
21
21
  import { fileURLToPath } from "url";
22
22
  import { mkdir } from "fs/promises";
23
23
  import os from "node:os";
24
- //#region ../types/dist/event-category-XfKNtfCc.mjs
24
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
25
25
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
26
26
  EventCategory["SystemBoot"] = "system.boot";
27
27
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -37,6 +37,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
37
37
  */
38
38
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
39
39
  /**
40
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
41
+ * Emitted only when the material on disk actually changed, so an
42
+ * operator who trusted the old certificate by hand is told rather than
43
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
44
+ *
45
+ * Rule: docs/decisions/adr-0227-*.md
46
+ */
47
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
48
+ /**
40
49
  * A newer addon or server-root package version was found by the
41
50
  * authoritative registry check. Emitted once when any observed
42
51
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8220,6 +8229,15 @@ var LabelDefinitionSchema = object({
8220
8229
  description: string().optional(),
8221
8230
  icon: string().optional()
8222
8231
  });
8232
+ var ClassMapDefinitionSchema = object({
8233
+ mapping: record(string(), _enum([
8234
+ "person",
8235
+ "vehicle",
8236
+ "animal",
8237
+ "package"
8238
+ ])),
8239
+ preserveOriginal: boolean()
8240
+ });
8223
8241
  var MODEL_FORMATS = [
8224
8242
  "onnx",
8225
8243
  "coreml",
@@ -8398,7 +8416,13 @@ var ModelCatalogEntrySchema = object({
8398
8416
  * `id` stays the source of truth for resolution/download/persistence; grouping
8399
8417
  * is a presentation overlay resolved back to an `id`.
8400
8418
  */
8401
- group: ModelVariantGroupSchema.optional()
8419
+ group: ModelVariantGroupSchema.optional(),
8420
+ /**
8421
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8422
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8423
+ * labels already ARE the CamStack macros (Scrypted identity map).
8424
+ */
8425
+ classMap: ClassMapDefinitionSchema.optional()
8402
8426
  });
8403
8427
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8404
8428
  format: literal("openvino"),
@@ -8427,7 +8451,8 @@ var ModelConvertMetadataSchema = object({
8427
8451
  "ocr",
8428
8452
  "segmentation"
8429
8453
  ]),
8430
- faceAlignment: boolean().optional()
8454
+ faceAlignment: boolean().optional(),
8455
+ classMap: ClassMapDefinitionSchema.optional()
8431
8456
  });
8432
8457
  var ConvertResultSchema = object({
8433
8458
  entry: ModelCatalogEntrySchema,
@@ -17732,6 +17757,33 @@ var NativeCropRefSchema = object({
17732
17757
  h: number()
17733
17758
  })
17734
17759
  });
17760
+ object({
17761
+ crop: object({
17762
+ left: number(),
17763
+ top: number(),
17764
+ width: number().positive(),
17765
+ height: number().positive()
17766
+ }).optional(),
17767
+ content: object({
17768
+ width: number().int().positive(),
17769
+ height: number().int().positive()
17770
+ }),
17771
+ fit: _enum(["stretch", "contain"]),
17772
+ format: _enum([
17773
+ "rgb",
17774
+ "gray",
17775
+ "jpeg"
17776
+ ])
17777
+ });
17778
+ var FrameRefSchema = object({
17779
+ registryId: string().min(1),
17780
+ id: string().min(1),
17781
+ width: number().int().positive(),
17782
+ height: number().int().positive(),
17783
+ format: _enum(["rgb", "gray"]),
17784
+ timestamp: number(),
17785
+ capturedAt: number().optional()
17786
+ });
17735
17787
  var ModelFormatSchema$1 = _enum([
17736
17788
  "onnx",
17737
17789
  "coreml",
@@ -17976,6 +18028,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17976
18028
  steps: array(PipelineStepInputSchema).min(1),
17977
18029
  frame: FrameInputSchema.optional(),
17978
18030
  /**
18031
+ * Process-local lazy frame. Valid only when caller and provider resolve
18032
+ * in the same execution-group process; split/cross-node callers use
18033
+ * `frame`/`image` inline compatibility instead.
18034
+ */
18035
+ frameRef: FrameRefSchema.optional(),
18036
+ /**
17979
18037
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17980
18038
  * the decoded pixels live in. One more member of the one-of
17981
18039
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18271,7 +18329,10 @@ var NativeCropResultSchema = object({
18271
18329
  * Which source served this crop, so a quality-sensitive consumer (the native
18272
18330
  * `keyFrame`) can reject a degraded fallback:
18273
18331
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18274
- * quality path).
18332
+ * quality path). A subject-tile serve is also native-resolution and stays
18333
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18334
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18335
+ * internal crop result (`nativeHits` vs `tileHits`).
18275
18336
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18276
18337
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18277
18338
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18762,12 +18823,41 @@ var RunnerLocalLoadSchema = object({
18762
18823
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18763
18824
  * working unchanged when they switch to reading from the runner cap.
18764
18825
  */
18826
+ var FrameLazyCountersSchema = object({
18827
+ framesDecoded: number(),
18828
+ framesAdmitted: number(),
18829
+ framesDroppedPixelFree: number(),
18830
+ viewsMaterialized: number(),
18831
+ viewsSkipped: number(),
18832
+ workerToRunnerBytes: number(),
18833
+ runnerToPoolRawBytes: number(),
18834
+ runnerToPoolJpegBytes: number(),
18835
+ onDemandFullFrameRequests: number(),
18836
+ onDemandCropRequests: number(),
18837
+ nativeHits: number(),
18838
+ nativeMisses: number(),
18839
+ tileHits: number(),
18840
+ tileMisses: number(),
18841
+ fallbackHits: number(),
18842
+ fallbackMisses: number(),
18843
+ retainedWritesAvoided: number(),
18844
+ residentRefs: number(),
18845
+ residentBytes: number(),
18846
+ releases: number(),
18847
+ evictions: number(),
18848
+ staleMisses: number()
18849
+ });
18850
+ var FrameLazyMetricsSchema = object({
18851
+ node: FrameLazyCountersSchema,
18852
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18853
+ });
18765
18854
  var RunnerLocalMetricsSchema = object({
18766
18855
  nodeId: string(),
18767
18856
  activeCameras: number(),
18768
18857
  throttledCameras: number(),
18769
18858
  avgInferenceTimeMs: number(),
18770
- queueDepth: number()
18859
+ queueDepth: number(),
18860
+ frameLazy: FrameLazyMetricsSchema.optional()
18771
18861
  });
18772
18862
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18773
18863
  handle: FrameHandleSchema,
@@ -20171,6 +20261,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
20171
20261
  location: StorageLocationSchema,
20172
20262
  relativePath: string()
20173
20263
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
20264
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20265
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20266
+ var ProfileSettingsBagSchema = record(string(), unknown());
20174
20267
  /**
20175
20268
  * A live terminal session hosted by the provider addon. Output and input do
20176
20269
  * NOT flow through the capability — they use the addon data plane
@@ -20200,7 +20293,14 @@ var TerminalSessionInfoSchema = object({
20200
20293
  var TerminalProfileInfoSchema = object({
20201
20294
  profileId: string(),
20202
20295
  label: string(),
20203
- description: string().optional()
20296
+ description: string().optional(),
20297
+ /** Spawn defaults the instance form copies on create. */
20298
+ executable: string().optional(),
20299
+ args: array(string()).readonly().optional(),
20300
+ cwd: string().optional(),
20301
+ environment: array(string()).readonly().optional(),
20302
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20303
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
20204
20304
  });
20205
20305
  /**
20206
20306
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20213,7 +20313,12 @@ var TerminalInstanceInfoSchema = object({
20213
20313
  profileId: string(),
20214
20314
  profileLabel: string(),
20215
20315
  name: string(),
20216
- enabled: boolean()
20316
+ enabled: boolean(),
20317
+ executable: string(),
20318
+ args: array(string()).readonly(),
20319
+ cwd: string(),
20320
+ environment: array(string()).readonly(),
20321
+ profileSettings: ProfileSettingsBagSchema
20217
20322
  });
20218
20323
  var TerminalLegacyCameraSchema = object({
20219
20324
  stableId: string(),
@@ -20243,7 +20348,23 @@ var TerminalOutputBatchSchema = object({
20243
20348
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
20244
20349
  targetNodeId: string().min(1),
20245
20350
  profileId: string().min(1),
20246
- name: string().trim().min(1).max(160).optional()
20351
+ name: string().trim().min(1).max(160).optional(),
20352
+ executable: string().max(1024).optional(),
20353
+ args: array(string().max(2048)).max(64).optional(),
20354
+ cwd: string().max(1024).optional(),
20355
+ environment: array(string().max(4096)).max(64).optional(),
20356
+ profileSettings: ProfileSettingsBagSchema.optional()
20357
+ }), TerminalInstanceInfoSchema, {
20358
+ kind: "mutation",
20359
+ auth: "admin"
20360
+ }), method(object({
20361
+ instanceId: string().min(1),
20362
+ name: string().trim().min(1).max(160).optional(),
20363
+ executable: string().max(1024).optional(),
20364
+ args: array(string().max(2048)).max(64).optional(),
20365
+ cwd: string().max(1024).optional(),
20366
+ environment: array(string().max(4096)).max(64).optional(),
20367
+ profileSettings: ProfileSettingsBagSchema.optional()
20247
20368
  }), TerminalInstanceInfoSchema, {
20248
20369
  kind: "mutation",
20249
20370
  auth: "admin"
@@ -20265,7 +20386,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20265
20386
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20266
20387
  profileId: string(),
20267
20388
  cols: number().int().positive(),
20268
- rows: number().int().positive()
20389
+ rows: number().int().positive(),
20390
+ executable: string().max(1024).optional(),
20391
+ args: array(string().max(2048)).max(64).optional(),
20392
+ cwd: string().max(1024).optional(),
20393
+ environment: array(string().max(4096)).max(64).optional()
20269
20394
  }), TerminalSessionInfoSchema, {
20270
20395
  kind: "mutation",
20271
20396
  auth: "admin"
@@ -24140,10 +24265,10 @@ var lawnMowerControlCapability = {
24140
24265
  *
24141
24266
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
24142
24267
  * to receive an ordered list of candidate base URLs it should race
24143
- * on connect — LAN IPv4 first (lowest latency when on same network),
24144
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
24145
- * race them with short timeouts and stick with the winner for the
24146
- * session.
24268
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24269
+ * when on the same network), then public hostname (if a tunnel is
24270
+ * up). The SDK can race them with short timeouts and stick with the
24271
+ * winner for the session.
24147
24272
  *
24148
24273
  * Why hub-only: agents are not directly addressable by the operator's
24149
24274
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24298,6 +24423,17 @@ var NotificationEndpointSchema = object({
24298
24423
  /** What the ranking currently resolves to (null when nothing is reachable). */
24299
24424
  resolved: string().nullable()
24300
24425
  });
24426
+ /**
24427
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24428
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24429
+ * currently expands to, so the UI can show the effective set either way.
24430
+ */
24431
+ var ViewerEndpointsSchema = object({
24432
+ /** The operator's explicit race set, or empty for AUTO. */
24433
+ baseUrls: array(string()).readonly(),
24434
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24435
+ resolved: array(string()).readonly()
24436
+ });
24301
24437
  var AllowedAddressesSchema = object({
24302
24438
  /**
24303
24439
  * Allowlist of interface addresses operators have explicitly opted
@@ -24306,6 +24442,20 @@ var AllowedAddressesSchema = object({
24306
24442
  * Network Addresses admin page and persisted by the addon.
24307
24443
  */
24308
24444
  addresses: array(string()).readonly() });
24445
+ var TlsStatusSchema = object({
24446
+ mode: _enum([
24447
+ "generated",
24448
+ "uploaded",
24449
+ "disabled"
24450
+ ]),
24451
+ leafFingerprintSha256: string().nullable(),
24452
+ caFingerprintSha256: string().nullable(),
24453
+ validTo: string().nullable(),
24454
+ sans: array(string()),
24455
+ caCertPem: string().nullable(),
24456
+ reissueError: string().nullable(),
24457
+ restartRequired: boolean()
24458
+ });
24309
24459
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24310
24460
  /**
24311
24461
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24315,17 +24465,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24315
24465
  */
24316
24466
  port: number().int().min(1).max(65535).optional(),
24317
24467
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24318
- * candidate. Default `true`. */
24468
+ * candidate. Default `false` — loopback is not a client route. */
24319
24469
  includeLoopback: boolean().optional(),
24320
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24321
- * Default `false`. */
24470
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24471
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24472
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24322
24473
  ipv4Only: boolean().optional(),
24323
24474
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24324
24475
  * Pass `'https'` when the caller is itself loaded over HTTPS
24325
24476
  * to avoid mixed-content blocks in the browser. The public
24326
24477
  * tunnel always emits `https://` regardless. */
24327
24478
  scheme: _enum(["http", "https"]).optional()
24328
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
24479
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24480
+ kind: "mutation",
24481
+ auth: "admin"
24482
+ }), method(object({
24483
+ certPem: string().min(1),
24484
+ keyPem: string().min(1),
24485
+ caPem: string().optional()
24486
+ }), TlsStatusSchema, {
24487
+ kind: "mutation",
24488
+ auth: "admin"
24489
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24490
+ kind: "mutation",
24491
+ auth: "admin"
24492
+ });
24329
24493
  var LockControlStatusSchema = object({
24330
24494
  /** Lifecycle state of the lock. `jammed` means the motor reported
24331
24495
  * failure to reach the target — operator intervention required. */
@@ -32804,6 +32968,12 @@ Object.freeze({
32804
32968
  addonId: null,
32805
32969
  access: "create"
32806
32970
  },
32971
+ "localNetwork.downloadCa": {
32972
+ capName: "local-network",
32973
+ capScope: "system",
32974
+ addonId: null,
32975
+ access: "view"
32976
+ },
32807
32977
  "localNetwork.getAllowedAddresses": {
32808
32978
  capName: "local-network",
32809
32979
  capScope: "system",
@@ -32828,18 +32998,42 @@ Object.freeze({
32828
32998
  addonId: null,
32829
32999
  access: "view"
32830
33000
  },
33001
+ "localNetwork.getTlsStatus": {
33002
+ capName: "local-network",
33003
+ capScope: "system",
33004
+ addonId: null,
33005
+ access: "view"
33006
+ },
33007
+ "localNetwork.getViewerEndpoints": {
33008
+ capName: "local-network",
33009
+ capScope: "system",
33010
+ addonId: null,
33011
+ access: "view"
33012
+ },
32831
33013
  "localNetwork.list": {
32832
33014
  capName: "local-network",
32833
33015
  capScope: "system",
32834
33016
  addonId: null,
32835
33017
  access: "view"
32836
33018
  },
33019
+ "localNetwork.regenerateCertificate": {
33020
+ capName: "local-network",
33021
+ capScope: "system",
33022
+ addonId: null,
33023
+ access: "create"
33024
+ },
32837
33025
  "localNetwork.resetAllowlistToBestMatch": {
32838
33026
  capName: "local-network",
32839
33027
  capScope: "system",
32840
33028
  addonId: null,
32841
33029
  access: "delete"
32842
33030
  },
33031
+ "localNetwork.revertToGeneratedCertificate": {
33032
+ capName: "local-network",
33033
+ capScope: "system",
33034
+ addonId: null,
33035
+ access: "create"
33036
+ },
32843
33037
  "localNetwork.setAllowedAddresses": {
32844
33038
  capName: "local-network",
32845
33039
  capScope: "system",
@@ -32852,6 +33046,18 @@ Object.freeze({
32852
33046
  addonId: null,
32853
33047
  access: "create"
32854
33048
  },
33049
+ "localNetwork.setViewerEndpoints": {
33050
+ capName: "local-network",
33051
+ capScope: "system",
33052
+ addonId: null,
33053
+ access: "create"
33054
+ },
33055
+ "localNetwork.uploadCertificate": {
33056
+ capName: "local-network",
33057
+ capScope: "system",
33058
+ addonId: null,
33059
+ access: "create"
33060
+ },
32855
33061
  "lockControl.lock": {
32856
33062
  capName: "lock-control",
32857
33063
  capScope: "device",
@@ -35732,6 +35938,12 @@ Object.freeze({
35732
35938
  addonId: null,
35733
35939
  access: "create"
35734
35940
  },
35941
+ "terminalSession.updateInstance": {
35942
+ capName: "terminal-session",
35943
+ capScope: "system",
35944
+ addonId: null,
35945
+ access: "create"
35946
+ },
35735
35947
  "terminalSession.writeInput": {
35736
35948
  capName: "terminal-session",
35737
35949
  capScope: "system",
@@ -38219,6 +38431,35 @@ Object.freeze(Object.fromEntries([{
38219
38431
  }]
38220
38432
  }].map((s) => [s.stepId, s.defaultModelId])));
38221
38433
  string().min(1);
38434
+ var CLUSTER_STEP_SETTING_FIELDS = [{
38435
+ stepId: "face-embedding",
38436
+ key: "minLandmarkFaceSize",
38437
+ label: "Min face size for recognition (detection px)",
38438
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
38439
+ type: "slider",
38440
+ min: 0,
38441
+ max: 64,
38442
+ step: 2,
38443
+ default: 24
38444
+ }];
38445
+ function clusterStepSettingKey(stepId, fieldKey) {
38446
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
38447
+ }
38448
+ var ClusterSettingNumberSchema = number().finite();
38449
+ function readClusterStepSettings(config) {
38450
+ const out = {};
38451
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
38452
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
38453
+ const value = parsed.success ? parsed.data : field.default;
38454
+ const existing = out[field.stepId] ?? {};
38455
+ out[field.stepId] = {
38456
+ ...existing,
38457
+ [field.key]: value
38458
+ };
38459
+ }
38460
+ return out;
38461
+ }
38462
+ readClusterStepSettings({});
38222
38463
  object({
38223
38464
  /**
38224
38465
  * Fraction of the box's own size added on EACH side before cutting.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.47",
3
+ "version": "1.2.49",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",