@camstack/addon-provider-dreame 0.2.25 → 0.2.27

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
@@ -50504,7 +50504,7 @@ objectType({
50504
50504
  })
50505
50505
  });
50506
50506
  //#endregion
50507
- //#region ../types/dist/event-category-XfKNtfCc.mjs
50507
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
50508
50508
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
50509
50509
  EventCategory["SystemBoot"] = "system.boot";
50510
50510
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -50520,6 +50520,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
50520
50520
  */
50521
50521
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
50522
50522
  /**
50523
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
50524
+ * Emitted only when the material on disk actually changed, so an
50525
+ * operator who trusted the old certificate by hand is told rather than
50526
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
50527
+ *
50528
+ * Rule: docs/decisions/adr-0227-*.md
50529
+ */
50530
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
50531
+ /**
50523
50532
  * A newer addon or server-root package version was found by the
50524
50533
  * authoritative registry check. Emitted once when any observed
50525
50534
  * `latestVersion` changes (or a package/node first appears behind);
@@ -58516,6 +58525,15 @@ var LabelDefinitionSchema = object({
58516
58525
  description: string().optional(),
58517
58526
  icon: string().optional()
58518
58527
  });
58528
+ var ClassMapDefinitionSchema = object({
58529
+ mapping: record(string(), _enum([
58530
+ "person",
58531
+ "vehicle",
58532
+ "animal",
58533
+ "package"
58534
+ ])),
58535
+ preserveOriginal: boolean()
58536
+ });
58519
58537
  var MODEL_FORMATS = [
58520
58538
  "onnx",
58521
58539
  "coreml",
@@ -58694,7 +58712,13 @@ var ModelCatalogEntrySchema = object({
58694
58712
  * `id` stays the source of truth for resolution/download/persistence; grouping
58695
58713
  * is a presentation overlay resolved back to an `id`.
58696
58714
  */
58697
- group: ModelVariantGroupSchema.optional()
58715
+ group: ModelVariantGroupSchema.optional(),
58716
+ /**
58717
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
58718
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
58719
+ * labels already ARE the CamStack macros (Scrypted identity map).
58720
+ */
58721
+ classMap: ClassMapDefinitionSchema.optional()
58698
58722
  });
58699
58723
  var ConvertTargetSchema = discriminatedUnion("format", [object({
58700
58724
  format: literal("openvino"),
@@ -58723,7 +58747,8 @@ var ModelConvertMetadataSchema = object({
58723
58747
  "ocr",
58724
58748
  "segmentation"
58725
58749
  ]),
58726
- faceAlignment: boolean().optional()
58750
+ faceAlignment: boolean().optional(),
58751
+ classMap: ClassMapDefinitionSchema.optional()
58727
58752
  });
58728
58753
  var ConvertResultSchema = object({
58729
58754
  entry: ModelCatalogEntrySchema,
@@ -68033,6 +68058,33 @@ var NativeCropRefSchema = object({
68033
68058
  h: number()
68034
68059
  })
68035
68060
  });
68061
+ object({
68062
+ crop: object({
68063
+ left: number(),
68064
+ top: number(),
68065
+ width: number().positive(),
68066
+ height: number().positive()
68067
+ }).optional(),
68068
+ content: object({
68069
+ width: number().int().positive(),
68070
+ height: number().int().positive()
68071
+ }),
68072
+ fit: _enum(["stretch", "contain"]),
68073
+ format: _enum([
68074
+ "rgb",
68075
+ "gray",
68076
+ "jpeg"
68077
+ ])
68078
+ });
68079
+ var FrameRefSchema = object({
68080
+ registryId: string().min(1),
68081
+ id: string().min(1),
68082
+ width: number().int().positive(),
68083
+ height: number().int().positive(),
68084
+ format: _enum(["rgb", "gray"]),
68085
+ timestamp: number(),
68086
+ capturedAt: number().optional()
68087
+ });
68036
68088
  var ModelFormatSchema$1 = _enum([
68037
68089
  "onnx",
68038
68090
  "coreml",
@@ -68277,6 +68329,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
68277
68329
  steps: array(PipelineStepInputSchema).min(1),
68278
68330
  frame: FrameInputSchema.optional(),
68279
68331
  /**
68332
+ * Process-local lazy frame. Valid only when caller and provider resolve
68333
+ * in the same execution-group process; split/cross-node callers use
68334
+ * `frame`/`image` inline compatibility instead.
68335
+ */
68336
+ frameRef: FrameRefSchema.optional(),
68337
+ /**
68280
68338
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
68281
68339
  * the decoded pixels live in. One more member of the one-of
68282
68340
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -68572,7 +68630,10 @@ var NativeCropResultSchema = object({
68572
68630
  * Which source served this crop, so a quality-sensitive consumer (the native
68573
68631
  * `keyFrame`) can reject a degraded fallback:
68574
68632
  * - `native` — cut from the decode worker's retained NATIVE surface (the
68575
- * quality path).
68633
+ * quality path). A subject-tile serve is also native-resolution and stays
68634
+ * `native` here: the public enum cannot name `tile` without a breaking cap
68635
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
68636
+ * internal crop result (`nativeHits` vs `tileHits`).
68576
68637
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
68577
68638
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
68578
68639
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -69063,12 +69124,41 @@ var RunnerLocalLoadSchema = object({
69063
69124
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
69064
69125
  * working unchanged when they switch to reading from the runner cap.
69065
69126
  */
69127
+ var FrameLazyCountersSchema = object({
69128
+ framesDecoded: number(),
69129
+ framesAdmitted: number(),
69130
+ framesDroppedPixelFree: number(),
69131
+ viewsMaterialized: number(),
69132
+ viewsSkipped: number(),
69133
+ workerToRunnerBytes: number(),
69134
+ runnerToPoolRawBytes: number(),
69135
+ runnerToPoolJpegBytes: number(),
69136
+ onDemandFullFrameRequests: number(),
69137
+ onDemandCropRequests: number(),
69138
+ nativeHits: number(),
69139
+ nativeMisses: number(),
69140
+ tileHits: number(),
69141
+ tileMisses: number(),
69142
+ fallbackHits: number(),
69143
+ fallbackMisses: number(),
69144
+ retainedWritesAvoided: number(),
69145
+ residentRefs: number(),
69146
+ residentBytes: number(),
69147
+ releases: number(),
69148
+ evictions: number(),
69149
+ staleMisses: number()
69150
+ });
69151
+ var FrameLazyMetricsSchema = object({
69152
+ node: FrameLazyCountersSchema,
69153
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
69154
+ });
69066
69155
  var RunnerLocalMetricsSchema = object({
69067
69156
  nodeId: string(),
69068
69157
  activeCameras: number(),
69069
69158
  throttledCameras: number(),
69070
69159
  avgInferenceTimeMs: number(),
69071
- queueDepth: number()
69160
+ queueDepth: number(),
69161
+ frameLazy: FrameLazyMetricsSchema.optional()
69072
69162
  });
69073
69163
  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({
69074
69164
  handle: FrameHandleSchema,
@@ -70368,6 +70458,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
70368
70458
  location: StorageLocationSchema,
70369
70459
  relativePath: string()
70370
70460
  }), _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" });
70461
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
70462
+ var ProfileSettingsSchemaBridge = unknown().nullable();
70463
+ var ProfileSettingsBagSchema = record(string(), unknown());
70371
70464
  /**
70372
70465
  * A live terminal session hosted by the provider addon. Output and input do
70373
70466
  * NOT flow through the capability — they use the addon data plane
@@ -70397,7 +70490,14 @@ var TerminalSessionInfoSchema = object({
70397
70490
  var TerminalProfileInfoSchema = object({
70398
70491
  profileId: string(),
70399
70492
  label: string(),
70400
- description: string().optional()
70493
+ description: string().optional(),
70494
+ /** Spawn defaults the instance form copies on create. */
70495
+ executable: string().optional(),
70496
+ args: array(string()).readonly().optional(),
70497
+ cwd: string().optional(),
70498
+ environment: array(string()).readonly().optional(),
70499
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
70500
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
70401
70501
  });
70402
70502
  /**
70403
70503
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -70410,7 +70510,12 @@ var TerminalInstanceInfoSchema = object({
70410
70510
  profileId: string(),
70411
70511
  profileLabel: string(),
70412
70512
  name: string(),
70413
- enabled: boolean()
70513
+ enabled: boolean(),
70514
+ executable: string(),
70515
+ args: array(string()).readonly(),
70516
+ cwd: string(),
70517
+ environment: array(string()).readonly(),
70518
+ profileSettings: ProfileSettingsBagSchema
70414
70519
  });
70415
70520
  var TerminalLegacyCameraSchema = object({
70416
70521
  stableId: string(),
@@ -70440,7 +70545,23 @@ var TerminalOutputBatchSchema = object({
70440
70545
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
70441
70546
  targetNodeId: string().min(1),
70442
70547
  profileId: string().min(1),
70443
- name: string().trim().min(1).max(160).optional()
70548
+ name: string().trim().min(1).max(160).optional(),
70549
+ executable: string().max(1024).optional(),
70550
+ args: array(string().max(2048)).max(64).optional(),
70551
+ cwd: string().max(1024).optional(),
70552
+ environment: array(string().max(4096)).max(64).optional(),
70553
+ profileSettings: ProfileSettingsBagSchema.optional()
70554
+ }), TerminalInstanceInfoSchema, {
70555
+ kind: "mutation",
70556
+ auth: "admin"
70557
+ }), method(object({
70558
+ instanceId: string().min(1),
70559
+ name: string().trim().min(1).max(160).optional(),
70560
+ executable: string().max(1024).optional(),
70561
+ args: array(string().max(2048)).max(64).optional(),
70562
+ cwd: string().max(1024).optional(),
70563
+ environment: array(string().max(4096)).max(64).optional(),
70564
+ profileSettings: ProfileSettingsBagSchema.optional()
70444
70565
  }), TerminalInstanceInfoSchema, {
70445
70566
  kind: "mutation",
70446
70567
  auth: "admin"
@@ -70462,7 +70583,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
70462
70583
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
70463
70584
  profileId: string(),
70464
70585
  cols: number().int().positive(),
70465
- rows: number().int().positive()
70586
+ rows: number().int().positive(),
70587
+ executable: string().max(1024).optional(),
70588
+ args: array(string().max(2048)).max(64).optional(),
70589
+ cwd: string().max(1024).optional(),
70590
+ environment: array(string().max(4096)).max(64).optional()
70466
70591
  }), TerminalSessionInfoSchema, {
70467
70592
  kind: "mutation",
70468
70593
  auth: "admin"
@@ -74362,10 +74487,10 @@ var lawnMowerControlCapability = {
74362
74487
  *
74363
74488
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
74364
74489
  * to receive an ordered list of candidate base URLs it should race
74365
- * on connect — LAN IPv4 first (lowest latency when on same network),
74366
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
74367
- * race them with short timeouts and stick with the winner for the
74368
- * session.
74490
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
74491
+ * when on the same network), then public hostname (if a tunnel is
74492
+ * up). The SDK can race them with short timeouts and stick with the
74493
+ * winner for the session.
74369
74494
  *
74370
74495
  * Why hub-only: agents are not directly addressable by the operator's
74371
74496
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -74520,6 +74645,17 @@ var NotificationEndpointSchema = object({
74520
74645
  /** What the ranking currently resolves to (null when nothing is reachable). */
74521
74646
  resolved: string().nullable()
74522
74647
  });
74648
+ /**
74649
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
74650
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
74651
+ * currently expands to, so the UI can show the effective set either way.
74652
+ */
74653
+ var ViewerEndpointsSchema = object({
74654
+ /** The operator's explicit race set, or empty for AUTO. */
74655
+ baseUrls: array(string()).readonly(),
74656
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
74657
+ resolved: array(string()).readonly()
74658
+ });
74523
74659
  var AllowedAddressesSchema = object({
74524
74660
  /**
74525
74661
  * Allowlist of interface addresses operators have explicitly opted
@@ -74528,6 +74664,20 @@ var AllowedAddressesSchema = object({
74528
74664
  * Network Addresses admin page and persisted by the addon.
74529
74665
  */
74530
74666
  addresses: array(string()).readonly() });
74667
+ var TlsStatusSchema = object({
74668
+ mode: _enum([
74669
+ "generated",
74670
+ "uploaded",
74671
+ "disabled"
74672
+ ]),
74673
+ leafFingerprintSha256: string().nullable(),
74674
+ caFingerprintSha256: string().nullable(),
74675
+ validTo: string().nullable(),
74676
+ sans: array(string()),
74677
+ caCertPem: string().nullable(),
74678
+ reissueError: string().nullable(),
74679
+ restartRequired: boolean()
74680
+ });
74531
74681
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
74532
74682
  /**
74533
74683
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -74537,17 +74687,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
74537
74687
  */
74538
74688
  port: number().int().min(1).max(65535).optional(),
74539
74689
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
74540
- * candidate. Default `true`. */
74690
+ * candidate. Default `false` — loopback is not a client route. */
74541
74691
  includeLoopback: boolean().optional(),
74542
- /** Skip IPv6 entries. Some legacy clients can't parse them.
74543
- * Default `false`. */
74692
+ /** Skip IPv6 entries. Default `false` the palette includes stable
74693
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
74694
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
74544
74695
  ipv4Only: boolean().optional(),
74545
74696
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
74546
74697
  * Pass `'https'` when the caller is itself loaded over HTTPS
74547
74698
  * to avoid mixed-content blocks in the browser. The public
74548
74699
  * tunnel always emits `https://` regardless. */
74549
74700
  scheme: _enum(["http", "https"]).optional()
74550
- }), 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" });
74701
+ }), 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, {
74702
+ kind: "mutation",
74703
+ auth: "admin"
74704
+ }), method(object({
74705
+ certPem: string().min(1),
74706
+ keyPem: string().min(1),
74707
+ caPem: string().optional()
74708
+ }), TlsStatusSchema, {
74709
+ kind: "mutation",
74710
+ auth: "admin"
74711
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
74712
+ kind: "mutation",
74713
+ auth: "admin"
74714
+ });
74551
74715
  var LockControlStatusSchema = object({
74552
74716
  /** Lifecycle state of the lock. `jammed` means the motor reported
74553
74717
  * failure to reach the target — operator intervention required. */
@@ -82574,6 +82738,12 @@ Object.freeze({
82574
82738
  addonId: null,
82575
82739
  access: "create"
82576
82740
  },
82741
+ "localNetwork.downloadCa": {
82742
+ capName: "local-network",
82743
+ capScope: "system",
82744
+ addonId: null,
82745
+ access: "view"
82746
+ },
82577
82747
  "localNetwork.getAllowedAddresses": {
82578
82748
  capName: "local-network",
82579
82749
  capScope: "system",
@@ -82598,18 +82768,42 @@ Object.freeze({
82598
82768
  addonId: null,
82599
82769
  access: "view"
82600
82770
  },
82771
+ "localNetwork.getTlsStatus": {
82772
+ capName: "local-network",
82773
+ capScope: "system",
82774
+ addonId: null,
82775
+ access: "view"
82776
+ },
82777
+ "localNetwork.getViewerEndpoints": {
82778
+ capName: "local-network",
82779
+ capScope: "system",
82780
+ addonId: null,
82781
+ access: "view"
82782
+ },
82601
82783
  "localNetwork.list": {
82602
82784
  capName: "local-network",
82603
82785
  capScope: "system",
82604
82786
  addonId: null,
82605
82787
  access: "view"
82606
82788
  },
82789
+ "localNetwork.regenerateCertificate": {
82790
+ capName: "local-network",
82791
+ capScope: "system",
82792
+ addonId: null,
82793
+ access: "create"
82794
+ },
82607
82795
  "localNetwork.resetAllowlistToBestMatch": {
82608
82796
  capName: "local-network",
82609
82797
  capScope: "system",
82610
82798
  addonId: null,
82611
82799
  access: "delete"
82612
82800
  },
82801
+ "localNetwork.revertToGeneratedCertificate": {
82802
+ capName: "local-network",
82803
+ capScope: "system",
82804
+ addonId: null,
82805
+ access: "create"
82806
+ },
82613
82807
  "localNetwork.setAllowedAddresses": {
82614
82808
  capName: "local-network",
82615
82809
  capScope: "system",
@@ -82622,6 +82816,18 @@ Object.freeze({
82622
82816
  addonId: null,
82623
82817
  access: "create"
82624
82818
  },
82819
+ "localNetwork.setViewerEndpoints": {
82820
+ capName: "local-network",
82821
+ capScope: "system",
82822
+ addonId: null,
82823
+ access: "create"
82824
+ },
82825
+ "localNetwork.uploadCertificate": {
82826
+ capName: "local-network",
82827
+ capScope: "system",
82828
+ addonId: null,
82829
+ access: "create"
82830
+ },
82625
82831
  "lockControl.lock": {
82626
82832
  capName: "lock-control",
82627
82833
  capScope: "device",
@@ -85502,6 +85708,12 @@ Object.freeze({
85502
85708
  addonId: null,
85503
85709
  access: "create"
85504
85710
  },
85711
+ "terminalSession.updateInstance": {
85712
+ capName: "terminal-session",
85713
+ capScope: "system",
85714
+ addonId: null,
85715
+ access: "create"
85716
+ },
85505
85717
  "terminalSession.writeInput": {
85506
85718
  capName: "terminal-session",
85507
85719
  capScope: "system",
@@ -87989,6 +88201,35 @@ Object.freeze(Object.fromEntries([{
87989
88201
  }]
87990
88202
  }].map((s) => [s.stepId, s.defaultModelId])));
87991
88203
  string().min(1);
88204
+ var CLUSTER_STEP_SETTING_FIELDS = [{
88205
+ stepId: "face-embedding",
88206
+ key: "minLandmarkFaceSize",
88207
+ label: "Min face size for recognition (detection px)",
88208
+ 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.",
88209
+ type: "slider",
88210
+ min: 0,
88211
+ max: 64,
88212
+ step: 2,
88213
+ default: 24
88214
+ }];
88215
+ function clusterStepSettingKey(stepId, fieldKey) {
88216
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
88217
+ }
88218
+ var ClusterSettingNumberSchema = number().finite();
88219
+ function readClusterStepSettings(config) {
88220
+ const out = {};
88221
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
88222
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
88223
+ const value = parsed.success ? parsed.data : field.default;
88224
+ const existing = out[field.stepId] ?? {};
88225
+ out[field.stepId] = {
88226
+ ...existing,
88227
+ [field.key]: value
88228
+ };
88229
+ }
88230
+ return out;
88231
+ }
88232
+ readClusterStepSettings({});
87992
88233
  object({
87993
88234
  /**
87994
88235
  * Fraction of the box's own size added on EACH side before cutting.
package/dist/addon.mjs CHANGED
@@ -50504,7 +50504,7 @@ objectType({
50504
50504
  })
50505
50505
  });
50506
50506
  //#endregion
50507
- //#region ../types/dist/event-category-XfKNtfCc.mjs
50507
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
50508
50508
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
50509
50509
  EventCategory["SystemBoot"] = "system.boot";
50510
50510
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -50520,6 +50520,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
50520
50520
  */
50521
50521
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
50522
50522
  /**
50523
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
50524
+ * Emitted only when the material on disk actually changed, so an
50525
+ * operator who trusted the old certificate by hand is told rather than
50526
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
50527
+ *
50528
+ * Rule: docs/decisions/adr-0227-*.md
50529
+ */
50530
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
50531
+ /**
50523
50532
  * A newer addon or server-root package version was found by the
50524
50533
  * authoritative registry check. Emitted once when any observed
50525
50534
  * `latestVersion` changes (or a package/node first appears behind);
@@ -58516,6 +58525,15 @@ var LabelDefinitionSchema = object({
58516
58525
  description: string().optional(),
58517
58526
  icon: string().optional()
58518
58527
  });
58528
+ var ClassMapDefinitionSchema = object({
58529
+ mapping: record(string(), _enum([
58530
+ "person",
58531
+ "vehicle",
58532
+ "animal",
58533
+ "package"
58534
+ ])),
58535
+ preserveOriginal: boolean()
58536
+ });
58519
58537
  var MODEL_FORMATS = [
58520
58538
  "onnx",
58521
58539
  "coreml",
@@ -58694,7 +58712,13 @@ var ModelCatalogEntrySchema = object({
58694
58712
  * `id` stays the source of truth for resolution/download/persistence; grouping
58695
58713
  * is a presentation overlay resolved back to an `id`.
58696
58714
  */
58697
- group: ModelVariantGroupSchema.optional()
58715
+ group: ModelVariantGroupSchema.optional(),
58716
+ /**
58717
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
58718
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
58719
+ * labels already ARE the CamStack macros (Scrypted identity map).
58720
+ */
58721
+ classMap: ClassMapDefinitionSchema.optional()
58698
58722
  });
58699
58723
  var ConvertTargetSchema = discriminatedUnion("format", [object({
58700
58724
  format: literal("openvino"),
@@ -58723,7 +58747,8 @@ var ModelConvertMetadataSchema = object({
58723
58747
  "ocr",
58724
58748
  "segmentation"
58725
58749
  ]),
58726
- faceAlignment: boolean().optional()
58750
+ faceAlignment: boolean().optional(),
58751
+ classMap: ClassMapDefinitionSchema.optional()
58727
58752
  });
58728
58753
  var ConvertResultSchema = object({
58729
58754
  entry: ModelCatalogEntrySchema,
@@ -68033,6 +68058,33 @@ var NativeCropRefSchema = object({
68033
68058
  h: number()
68034
68059
  })
68035
68060
  });
68061
+ object({
68062
+ crop: object({
68063
+ left: number(),
68064
+ top: number(),
68065
+ width: number().positive(),
68066
+ height: number().positive()
68067
+ }).optional(),
68068
+ content: object({
68069
+ width: number().int().positive(),
68070
+ height: number().int().positive()
68071
+ }),
68072
+ fit: _enum(["stretch", "contain"]),
68073
+ format: _enum([
68074
+ "rgb",
68075
+ "gray",
68076
+ "jpeg"
68077
+ ])
68078
+ });
68079
+ var FrameRefSchema = object({
68080
+ registryId: string().min(1),
68081
+ id: string().min(1),
68082
+ width: number().int().positive(),
68083
+ height: number().int().positive(),
68084
+ format: _enum(["rgb", "gray"]),
68085
+ timestamp: number(),
68086
+ capturedAt: number().optional()
68087
+ });
68036
68088
  var ModelFormatSchema$1 = _enum([
68037
68089
  "onnx",
68038
68090
  "coreml",
@@ -68277,6 +68329,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
68277
68329
  steps: array(PipelineStepInputSchema).min(1),
68278
68330
  frame: FrameInputSchema.optional(),
68279
68331
  /**
68332
+ * Process-local lazy frame. Valid only when caller and provider resolve
68333
+ * in the same execution-group process; split/cross-node callers use
68334
+ * `frame`/`image` inline compatibility instead.
68335
+ */
68336
+ frameRef: FrameRefSchema.optional(),
68337
+ /**
68280
68338
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
68281
68339
  * the decoded pixels live in. One more member of the one-of
68282
68340
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -68572,7 +68630,10 @@ var NativeCropResultSchema = object({
68572
68630
  * Which source served this crop, so a quality-sensitive consumer (the native
68573
68631
  * `keyFrame`) can reject a degraded fallback:
68574
68632
  * - `native` — cut from the decode worker's retained NATIVE surface (the
68575
- * quality path).
68633
+ * quality path). A subject-tile serve is also native-resolution and stays
68634
+ * `native` here: the public enum cannot name `tile` without a breaking cap
68635
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
68636
+ * internal crop result (`nativeHits` vs `tileHits`).
68576
68637
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
68577
68638
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
68578
68639
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -69063,12 +69124,41 @@ var RunnerLocalLoadSchema = object({
69063
69124
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
69064
69125
  * working unchanged when they switch to reading from the runner cap.
69065
69126
  */
69127
+ var FrameLazyCountersSchema = object({
69128
+ framesDecoded: number(),
69129
+ framesAdmitted: number(),
69130
+ framesDroppedPixelFree: number(),
69131
+ viewsMaterialized: number(),
69132
+ viewsSkipped: number(),
69133
+ workerToRunnerBytes: number(),
69134
+ runnerToPoolRawBytes: number(),
69135
+ runnerToPoolJpegBytes: number(),
69136
+ onDemandFullFrameRequests: number(),
69137
+ onDemandCropRequests: number(),
69138
+ nativeHits: number(),
69139
+ nativeMisses: number(),
69140
+ tileHits: number(),
69141
+ tileMisses: number(),
69142
+ fallbackHits: number(),
69143
+ fallbackMisses: number(),
69144
+ retainedWritesAvoided: number(),
69145
+ residentRefs: number(),
69146
+ residentBytes: number(),
69147
+ releases: number(),
69148
+ evictions: number(),
69149
+ staleMisses: number()
69150
+ });
69151
+ var FrameLazyMetricsSchema = object({
69152
+ node: FrameLazyCountersSchema,
69153
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
69154
+ });
69066
69155
  var RunnerLocalMetricsSchema = object({
69067
69156
  nodeId: string(),
69068
69157
  activeCameras: number(),
69069
69158
  throttledCameras: number(),
69070
69159
  avgInferenceTimeMs: number(),
69071
- queueDepth: number()
69160
+ queueDepth: number(),
69161
+ frameLazy: FrameLazyMetricsSchema.optional()
69072
69162
  });
69073
69163
  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({
69074
69164
  handle: FrameHandleSchema,
@@ -70368,6 +70458,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
70368
70458
  location: StorageLocationSchema,
70369
70459
  relativePath: string()
70370
70460
  }), _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" });
70461
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
70462
+ var ProfileSettingsSchemaBridge = unknown().nullable();
70463
+ var ProfileSettingsBagSchema = record(string(), unknown());
70371
70464
  /**
70372
70465
  * A live terminal session hosted by the provider addon. Output and input do
70373
70466
  * NOT flow through the capability — they use the addon data plane
@@ -70397,7 +70490,14 @@ var TerminalSessionInfoSchema = object({
70397
70490
  var TerminalProfileInfoSchema = object({
70398
70491
  profileId: string(),
70399
70492
  label: string(),
70400
- description: string().optional()
70493
+ description: string().optional(),
70494
+ /** Spawn defaults the instance form copies on create. */
70495
+ executable: string().optional(),
70496
+ args: array(string()).readonly().optional(),
70497
+ cwd: string().optional(),
70498
+ environment: array(string()).readonly().optional(),
70499
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
70500
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
70401
70501
  });
70402
70502
  /**
70403
70503
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -70410,7 +70510,12 @@ var TerminalInstanceInfoSchema = object({
70410
70510
  profileId: string(),
70411
70511
  profileLabel: string(),
70412
70512
  name: string(),
70413
- enabled: boolean()
70513
+ enabled: boolean(),
70514
+ executable: string(),
70515
+ args: array(string()).readonly(),
70516
+ cwd: string(),
70517
+ environment: array(string()).readonly(),
70518
+ profileSettings: ProfileSettingsBagSchema
70414
70519
  });
70415
70520
  var TerminalLegacyCameraSchema = object({
70416
70521
  stableId: string(),
@@ -70440,7 +70545,23 @@ var TerminalOutputBatchSchema = object({
70440
70545
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
70441
70546
  targetNodeId: string().min(1),
70442
70547
  profileId: string().min(1),
70443
- name: string().trim().min(1).max(160).optional()
70548
+ name: string().trim().min(1).max(160).optional(),
70549
+ executable: string().max(1024).optional(),
70550
+ args: array(string().max(2048)).max(64).optional(),
70551
+ cwd: string().max(1024).optional(),
70552
+ environment: array(string().max(4096)).max(64).optional(),
70553
+ profileSettings: ProfileSettingsBagSchema.optional()
70554
+ }), TerminalInstanceInfoSchema, {
70555
+ kind: "mutation",
70556
+ auth: "admin"
70557
+ }), method(object({
70558
+ instanceId: string().min(1),
70559
+ name: string().trim().min(1).max(160).optional(),
70560
+ executable: string().max(1024).optional(),
70561
+ args: array(string().max(2048)).max(64).optional(),
70562
+ cwd: string().max(1024).optional(),
70563
+ environment: array(string().max(4096)).max(64).optional(),
70564
+ profileSettings: ProfileSettingsBagSchema.optional()
70444
70565
  }), TerminalInstanceInfoSchema, {
70445
70566
  kind: "mutation",
70446
70567
  auth: "admin"
@@ -70462,7 +70583,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
70462
70583
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
70463
70584
  profileId: string(),
70464
70585
  cols: number().int().positive(),
70465
- rows: number().int().positive()
70586
+ rows: number().int().positive(),
70587
+ executable: string().max(1024).optional(),
70588
+ args: array(string().max(2048)).max(64).optional(),
70589
+ cwd: string().max(1024).optional(),
70590
+ environment: array(string().max(4096)).max(64).optional()
70466
70591
  }), TerminalSessionInfoSchema, {
70467
70592
  kind: "mutation",
70468
70593
  auth: "admin"
@@ -74362,10 +74487,10 @@ var lawnMowerControlCapability = {
74362
74487
  *
74363
74488
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
74364
74489
  * to receive an ordered list of candidate base URLs it should race
74365
- * on connect — LAN IPv4 first (lowest latency when on same network),
74366
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
74367
- * race them with short timeouts and stick with the winner for the
74368
- * session.
74490
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
74491
+ * when on the same network), then public hostname (if a tunnel is
74492
+ * up). The SDK can race them with short timeouts and stick with the
74493
+ * winner for the session.
74369
74494
  *
74370
74495
  * Why hub-only: agents are not directly addressable by the operator's
74371
74496
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -74520,6 +74645,17 @@ var NotificationEndpointSchema = object({
74520
74645
  /** What the ranking currently resolves to (null when nothing is reachable). */
74521
74646
  resolved: string().nullable()
74522
74647
  });
74648
+ /**
74649
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
74650
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
74651
+ * currently expands to, so the UI can show the effective set either way.
74652
+ */
74653
+ var ViewerEndpointsSchema = object({
74654
+ /** The operator's explicit race set, or empty for AUTO. */
74655
+ baseUrls: array(string()).readonly(),
74656
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
74657
+ resolved: array(string()).readonly()
74658
+ });
74523
74659
  var AllowedAddressesSchema = object({
74524
74660
  /**
74525
74661
  * Allowlist of interface addresses operators have explicitly opted
@@ -74528,6 +74664,20 @@ var AllowedAddressesSchema = object({
74528
74664
  * Network Addresses admin page and persisted by the addon.
74529
74665
  */
74530
74666
  addresses: array(string()).readonly() });
74667
+ var TlsStatusSchema = object({
74668
+ mode: _enum([
74669
+ "generated",
74670
+ "uploaded",
74671
+ "disabled"
74672
+ ]),
74673
+ leafFingerprintSha256: string().nullable(),
74674
+ caFingerprintSha256: string().nullable(),
74675
+ validTo: string().nullable(),
74676
+ sans: array(string()),
74677
+ caCertPem: string().nullable(),
74678
+ reissueError: string().nullable(),
74679
+ restartRequired: boolean()
74680
+ });
74531
74681
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
74532
74682
  /**
74533
74683
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -74537,17 +74687,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
74537
74687
  */
74538
74688
  port: number().int().min(1).max(65535).optional(),
74539
74689
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
74540
- * candidate. Default `true`. */
74690
+ * candidate. Default `false` — loopback is not a client route. */
74541
74691
  includeLoopback: boolean().optional(),
74542
- /** Skip IPv6 entries. Some legacy clients can't parse them.
74543
- * Default `false`. */
74692
+ /** Skip IPv6 entries. Default `false` the palette includes stable
74693
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
74694
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
74544
74695
  ipv4Only: boolean().optional(),
74545
74696
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
74546
74697
  * Pass `'https'` when the caller is itself loaded over HTTPS
74547
74698
  * to avoid mixed-content blocks in the browser. The public
74548
74699
  * tunnel always emits `https://` regardless. */
74549
74700
  scheme: _enum(["http", "https"]).optional()
74550
- }), 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" });
74701
+ }), 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, {
74702
+ kind: "mutation",
74703
+ auth: "admin"
74704
+ }), method(object({
74705
+ certPem: string().min(1),
74706
+ keyPem: string().min(1),
74707
+ caPem: string().optional()
74708
+ }), TlsStatusSchema, {
74709
+ kind: "mutation",
74710
+ auth: "admin"
74711
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
74712
+ kind: "mutation",
74713
+ auth: "admin"
74714
+ });
74551
74715
  var LockControlStatusSchema = object({
74552
74716
  /** Lifecycle state of the lock. `jammed` means the motor reported
74553
74717
  * failure to reach the target — operator intervention required. */
@@ -82574,6 +82738,12 @@ Object.freeze({
82574
82738
  addonId: null,
82575
82739
  access: "create"
82576
82740
  },
82741
+ "localNetwork.downloadCa": {
82742
+ capName: "local-network",
82743
+ capScope: "system",
82744
+ addonId: null,
82745
+ access: "view"
82746
+ },
82577
82747
  "localNetwork.getAllowedAddresses": {
82578
82748
  capName: "local-network",
82579
82749
  capScope: "system",
@@ -82598,18 +82768,42 @@ Object.freeze({
82598
82768
  addonId: null,
82599
82769
  access: "view"
82600
82770
  },
82771
+ "localNetwork.getTlsStatus": {
82772
+ capName: "local-network",
82773
+ capScope: "system",
82774
+ addonId: null,
82775
+ access: "view"
82776
+ },
82777
+ "localNetwork.getViewerEndpoints": {
82778
+ capName: "local-network",
82779
+ capScope: "system",
82780
+ addonId: null,
82781
+ access: "view"
82782
+ },
82601
82783
  "localNetwork.list": {
82602
82784
  capName: "local-network",
82603
82785
  capScope: "system",
82604
82786
  addonId: null,
82605
82787
  access: "view"
82606
82788
  },
82789
+ "localNetwork.regenerateCertificate": {
82790
+ capName: "local-network",
82791
+ capScope: "system",
82792
+ addonId: null,
82793
+ access: "create"
82794
+ },
82607
82795
  "localNetwork.resetAllowlistToBestMatch": {
82608
82796
  capName: "local-network",
82609
82797
  capScope: "system",
82610
82798
  addonId: null,
82611
82799
  access: "delete"
82612
82800
  },
82801
+ "localNetwork.revertToGeneratedCertificate": {
82802
+ capName: "local-network",
82803
+ capScope: "system",
82804
+ addonId: null,
82805
+ access: "create"
82806
+ },
82613
82807
  "localNetwork.setAllowedAddresses": {
82614
82808
  capName: "local-network",
82615
82809
  capScope: "system",
@@ -82622,6 +82816,18 @@ Object.freeze({
82622
82816
  addonId: null,
82623
82817
  access: "create"
82624
82818
  },
82819
+ "localNetwork.setViewerEndpoints": {
82820
+ capName: "local-network",
82821
+ capScope: "system",
82822
+ addonId: null,
82823
+ access: "create"
82824
+ },
82825
+ "localNetwork.uploadCertificate": {
82826
+ capName: "local-network",
82827
+ capScope: "system",
82828
+ addonId: null,
82829
+ access: "create"
82830
+ },
82625
82831
  "lockControl.lock": {
82626
82832
  capName: "lock-control",
82627
82833
  capScope: "device",
@@ -85502,6 +85708,12 @@ Object.freeze({
85502
85708
  addonId: null,
85503
85709
  access: "create"
85504
85710
  },
85711
+ "terminalSession.updateInstance": {
85712
+ capName: "terminal-session",
85713
+ capScope: "system",
85714
+ addonId: null,
85715
+ access: "create"
85716
+ },
85505
85717
  "terminalSession.writeInput": {
85506
85718
  capName: "terminal-session",
85507
85719
  capScope: "system",
@@ -87989,6 +88201,35 @@ Object.freeze(Object.fromEntries([{
87989
88201
  }]
87990
88202
  }].map((s) => [s.stepId, s.defaultModelId])));
87991
88203
  string().min(1);
88204
+ var CLUSTER_STEP_SETTING_FIELDS = [{
88205
+ stepId: "face-embedding",
88206
+ key: "minLandmarkFaceSize",
88207
+ label: "Min face size for recognition (detection px)",
88208
+ 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.",
88209
+ type: "slider",
88210
+ min: 0,
88211
+ max: 64,
88212
+ step: 2,
88213
+ default: 24
88214
+ }];
88215
+ function clusterStepSettingKey(stepId, fieldKey) {
88216
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
88217
+ }
88218
+ var ClusterSettingNumberSchema = number().finite();
88219
+ function readClusterStepSettings(config) {
88220
+ const out = {};
88221
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
88222
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
88223
+ const value = parsed.success ? parsed.data : field.default;
88224
+ const existing = out[field.stepId] ?? {};
88225
+ out[field.stepId] = {
88226
+ ...existing,
88227
+ [field.key]: value
88228
+ };
88229
+ }
88230
+ return out;
88231
+ }
88232
+ readClusterStepSettings({});
87992
88233
  object({
87993
88234
  /**
87994
88235
  * 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-dreame",
3
- "version": "0.2.25",
3
+ "version": "0.2.27",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",