@camstack/addon-matter-broker 0.2.50 → 0.2.52

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.js CHANGED
@@ -7732,7 +7732,8 @@ var OpsLogReasonSchema = _enum([
7732
7732
  "quota",
7733
7733
  "manual",
7734
7734
  "operator",
7735
- "maintenance"
7735
+ "maintenance",
7736
+ "orphaned-device"
7736
7737
  ]);
7737
7738
  /** One audit row, shared verbatim by both domains. */
7738
7739
  var OpsLogEntrySchema = object({
@@ -13093,10 +13094,39 @@ var DevicePersistConfigPayloadSchema = object({
13093
13094
  deviceId: number(),
13094
13095
  data: record(string$2(), unknown())
13095
13096
  });
13097
+ /** What a migration actually did, per switch. `unreachable` is a first-class
13098
+ * answer: a camera that could not be asked is not a camera that was silenced. */
13099
+ var MigrateSwitchOutcomeSchema = _enum([
13100
+ "off",
13101
+ "on",
13102
+ "not-offered",
13103
+ "unreachable"
13104
+ ]);
13105
+ var MigrateSwitchReportSchema = object({
13106
+ deviceId: number(),
13107
+ switchId: CameraSwitchIdSchema,
13108
+ outcome: MigrateSwitchOutcomeSchema,
13109
+ detail: string$2().optional()
13110
+ });
13111
+ var MigrateDeviceResultSchema = object({
13112
+ sourceId: number(),
13113
+ targetId: number(),
13114
+ switches: array(MigrateSwitchReportSchema).readonly(),
13115
+ /** Switches that could NOT be turned off on the source. Empty is the only
13116
+ * value meaning the replaced hardware is quiet. */
13117
+ sourceStillLive: array(CameraSwitchIdSchema).readonly(),
13118
+ swapped: boolean()
13119
+ });
13096
13120
  method(object({
13097
13121
  addonId: string$2(),
13098
13122
  stableId: string$2()
13099
- }), object({ id: number() }), { kind: "mutation" }), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), { kind: "mutation" }), method(object({ deviceId: number() }), record(string$2(), unknown())), method(object({ deviceId: number() }), record(string$2(), unknown())), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
13123
+ }), object({ id: number() }), { kind: "mutation" }), method(object({
13124
+ sourceId: number(),
13125
+ targetId: number()
13126
+ }), MigrateDeviceResultSchema, {
13127
+ kind: "mutation",
13128
+ auth: "admin"
13129
+ }), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), { kind: "mutation" }), method(object({ deviceId: number() }), record(string$2(), unknown())), method(object({ deviceId: number() }), record(string$2(), unknown())), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
13100
13130
  deviceId: number(),
13101
13131
  name: string$2()
13102
13132
  }), _void(), {
@@ -19271,6 +19301,34 @@ var KeyEventsForDeviceSchema = object({
19271
19301
  deviceId: number(),
19272
19302
  events: array(KeyEventSchema).readonly()
19273
19303
  });
19304
+ /** One non-empty bucket of the timeline histogram. */
19305
+ var EventDensityBucketSchema = object({
19306
+ bucketStart: number(),
19307
+ motion: number().int(),
19308
+ object: number().int(),
19309
+ audio: number().int()
19310
+ });
19311
+ /**
19312
+ * One camera's row in a `getEventDensityBatch` answer.
19313
+ *
19314
+ * TWO facts, and the timeline draws them differently:
19315
+ *
19316
+ * - `read: 'read'` — the histogram for this camera. `buckets: []` is a real
19317
+ * claim: read, and nothing happened in the window.
19318
+ * - `read: 'unreadable'` — nobody could answer for this camera. `buckets` is
19319
+ * empty and that emptiness means NOTHING; the timeline must render unknown,
19320
+ * not quiet.
19321
+ *
19322
+ * Fanned out per camera the difference was free — one query rejected while the
19323
+ * others resolved — and collapsing to a batch is the exact moment it is lost
19324
+ * silently. Every requested id gets a row: a caller that asked for twelve and
19325
+ * got eleven cannot tell which one is missing, or that any is.
19326
+ */
19327
+ var EventDensityForDeviceSchema = object({
19328
+ deviceId: number(),
19329
+ read: _enum(["read", "unreadable"]),
19330
+ buckets: array(EventDensityBucketSchema).readonly()
19331
+ });
19274
19332
  object({
19275
19333
  trackId: string$2(),
19276
19334
  className: string$2(),
@@ -19581,12 +19639,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19581
19639
  since: number(),
19582
19640
  until: number(),
19583
19641
  bucketMs: number().int().positive()
19584
- }), array(object({
19585
- bucketStart: number(),
19586
- motion: number().int(),
19587
- object: number().int(),
19588
- audio: number().int()
19589
- })).readonly()), method(object({
19642
+ }), array(EventDensityBucketSchema).readonly()), method(object({
19643
+ deviceIds: array(number()).min(1).max(200),
19644
+ since: number(),
19645
+ until: number(),
19646
+ bucketMs: number().int().positive()
19647
+ }), array(EventDensityForDeviceSchema).readonly()), method(object({
19590
19648
  deviceId: number(),
19591
19649
  cutoffMs: number()
19592
19650
  }), object({
@@ -28628,6 +28686,39 @@ var RecordingDaysSchema = object({
28628
28686
  /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
28629
28687
  days: array(number())
28630
28688
  });
28689
+ /**
28690
+ * One camera's row in a `getAvailabilityBatch` answer.
28691
+ *
28692
+ * `ranges` is EXACTLY what `getAvailability` returns for that camera — the
28693
+ * batch collapses the transport, not the work — plus the one thing the singular
28694
+ * method never had to say:
28695
+ *
28696
+ * - `read: 'read'` — answered. `ranges: []` means "read, and this camera has
28697
+ * no footage in the window", which is a real claim.
28698
+ * - `read: 'unreadable'` — the recorder could not answer for this camera (its
28699
+ * calendar rung threw, its location is unmounted). `ranges` is empty and
28700
+ * that emptiness means NOTHING.
28701
+ *
28702
+ * A timeline that renders the second as the first tells an operator there is no
28703
+ * footage when the truth is that nobody looked.
28704
+ */
28705
+ var RecordingAvailabilityForDeviceSchema = object({
28706
+ deviceId: number(),
28707
+ read: _enum(["read", "unreadable"]),
28708
+ ranges: array(RecordingRangeSchema).readonly()
28709
+ });
28710
+ /**
28711
+ * One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
28712
+ * {@link RecordingAvailabilityForDeviceSchema}: `days: []` on a `'read'` row is
28713
+ * "no footage in this month", `read: 'unreadable'` is "nobody could look", and
28714
+ * the date-picker's day dots must not spell them the same.
28715
+ */
28716
+ var RecordingDaysForDeviceSchema = object({
28717
+ deviceId: number(),
28718
+ read: _enum(["read", "unreadable"]),
28719
+ /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
28720
+ days: array(number()).readonly()
28721
+ });
28631
28722
  var RecordingManifestSchema = object({
28632
28723
  deviceId: number(),
28633
28724
  /** Local filesystem path to the master playlist; null when no recording exists for the requested range. */
@@ -28862,6 +28953,13 @@ method(object({
28862
28953
  }), RecordingAvailabilitySchema, {
28863
28954
  kind: "query",
28864
28955
  auth: "protected"
28956
+ }), method(object({
28957
+ deviceIds: array(number()).min(1).max(200),
28958
+ fromMs: number(),
28959
+ toMs: number()
28960
+ }), array(RecordingAvailabilityForDeviceSchema).readonly(), {
28961
+ kind: "query",
28962
+ auth: "protected"
28865
28963
  }), method(object({
28866
28964
  deviceId: number(),
28867
28965
  fromMs: number(),
@@ -28870,6 +28968,14 @@ method(object({
28870
28968
  }), RecordingDaysSchema, {
28871
28969
  kind: "query",
28872
28970
  auth: "protected"
28971
+ }), method(object({
28972
+ deviceIds: array(number()).min(1).max(200),
28973
+ fromMs: number(),
28974
+ toMs: number(),
28975
+ tzOffsetMinutes: number()
28976
+ }), array(RecordingDaysForDeviceSchema).readonly(), {
28977
+ kind: "query",
28978
+ auth: "protected"
28873
28979
  }), method(object({
28874
28980
  deviceId: number(),
28875
28981
  fromMs: number(),
@@ -34032,6 +34138,12 @@ Object.freeze({
34032
34138
  addonId: null,
34033
34139
  access: "view"
34034
34140
  },
34141
+ "deviceManager.migrateDevice": {
34142
+ capName: "device-manager",
34143
+ capScope: "system",
34144
+ addonId: null,
34145
+ access: "create"
34146
+ },
34035
34147
  "deviceManager.persistConfig": {
34036
34148
  capName: "device-manager",
34037
34149
  capScope: "system",
@@ -35766,6 +35878,12 @@ Object.freeze({
35766
35878
  addonId: null,
35767
35879
  access: "view"
35768
35880
  },
35881
+ "pipelineAnalytics.getEventDensityBatch": {
35882
+ capName: "pipeline-analytics",
35883
+ capScope: "device",
35884
+ addonId: null,
35885
+ access: "view"
35886
+ },
35769
35887
  "pipelineAnalytics.getEventMedia": {
35770
35888
  capName: "pipeline-analytics",
35771
35889
  capScope: "device",
@@ -36888,12 +37006,24 @@ Object.freeze({
36888
37006
  addonId: null,
36889
37007
  access: "view"
36890
37008
  },
37009
+ "recording.getAvailabilityBatch": {
37010
+ capName: "recording",
37011
+ capScope: "system",
37012
+ addonId: null,
37013
+ access: "view"
37014
+ },
36891
37015
  "recording.getDaysWithRecordings": {
36892
37016
  capName: "recording",
36893
37017
  capScope: "system",
36894
37018
  addonId: null,
36895
37019
  access: "view"
36896
37020
  },
37021
+ "recording.getDaysWithRecordingsBatch": {
37022
+ capName: "recording",
37023
+ capScope: "system",
37024
+ addonId: null,
37025
+ access: "view"
37026
+ },
36897
37027
  "recording.getDeviceConfig": {
36898
37028
  capName: "recording",
36899
37029
  capScope: "system",
@@ -39498,6 +39628,11 @@ Object.freeze({
39498
39628
  form: "single",
39499
39629
  optional: false
39500
39630
  }],
39631
+ "pipelineAnalytics.getEventDensityBatch": [{
39632
+ name: "deviceIds",
39633
+ form: "array",
39634
+ optional: false
39635
+ }],
39501
39636
  "pipelineAnalytics.getEventMedia": [{
39502
39637
  name: "deviceId",
39503
39638
  form: "single",
@@ -39913,11 +40048,21 @@ Object.freeze({
39913
40048
  form: "single",
39914
40049
  optional: false
39915
40050
  }],
40051
+ "recording.getAvailabilityBatch": [{
40052
+ name: "deviceIds",
40053
+ form: "array",
40054
+ optional: false
40055
+ }],
39916
40056
  "recording.getDaysWithRecordings": [{
39917
40057
  name: "deviceId",
39918
40058
  form: "single",
39919
40059
  optional: false
39920
40060
  }],
40061
+ "recording.getDaysWithRecordingsBatch": [{
40062
+ name: "deviceIds",
40063
+ form: "array",
40064
+ optional: false
40065
+ }],
39921
40066
  "recording.getDeviceConfig": [{
39922
40067
  name: "deviceId",
39923
40068
  form: "single",
package/dist/addon.mjs CHANGED
@@ -7730,7 +7730,8 @@ var OpsLogReasonSchema = _enum([
7730
7730
  "quota",
7731
7731
  "manual",
7732
7732
  "operator",
7733
- "maintenance"
7733
+ "maintenance",
7734
+ "orphaned-device"
7734
7735
  ]);
7735
7736
  /** One audit row, shared verbatim by both domains. */
7736
7737
  var OpsLogEntrySchema = object({
@@ -13091,10 +13092,39 @@ var DevicePersistConfigPayloadSchema = object({
13091
13092
  deviceId: number(),
13092
13093
  data: record(string$2(), unknown())
13093
13094
  });
13095
+ /** What a migration actually did, per switch. `unreachable` is a first-class
13096
+ * answer: a camera that could not be asked is not a camera that was silenced. */
13097
+ var MigrateSwitchOutcomeSchema = _enum([
13098
+ "off",
13099
+ "on",
13100
+ "not-offered",
13101
+ "unreachable"
13102
+ ]);
13103
+ var MigrateSwitchReportSchema = object({
13104
+ deviceId: number(),
13105
+ switchId: CameraSwitchIdSchema,
13106
+ outcome: MigrateSwitchOutcomeSchema,
13107
+ detail: string$2().optional()
13108
+ });
13109
+ var MigrateDeviceResultSchema = object({
13110
+ sourceId: number(),
13111
+ targetId: number(),
13112
+ switches: array(MigrateSwitchReportSchema).readonly(),
13113
+ /** Switches that could NOT be turned off on the source. Empty is the only
13114
+ * value meaning the replaced hardware is quiet. */
13115
+ sourceStillLive: array(CameraSwitchIdSchema).readonly(),
13116
+ swapped: boolean()
13117
+ });
13094
13118
  method(object({
13095
13119
  addonId: string$2(),
13096
13120
  stableId: string$2()
13097
- }), object({ id: number() }), { kind: "mutation" }), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), { kind: "mutation" }), method(object({ deviceId: number() }), record(string$2(), unknown())), method(object({ deviceId: number() }), record(string$2(), unknown())), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
13121
+ }), object({ id: number() }), { kind: "mutation" }), method(object({
13122
+ sourceId: number(),
13123
+ targetId: number()
13124
+ }), MigrateDeviceResultSchema, {
13125
+ kind: "mutation",
13126
+ auth: "admin"
13127
+ }), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), { kind: "mutation" }), method(object({ deviceId: number() }), record(string$2(), unknown())), method(object({ deviceId: number() }), record(string$2(), unknown())), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
13098
13128
  deviceId: number(),
13099
13129
  name: string$2()
13100
13130
  }), _void(), {
@@ -19269,6 +19299,34 @@ var KeyEventsForDeviceSchema = object({
19269
19299
  deviceId: number(),
19270
19300
  events: array(KeyEventSchema).readonly()
19271
19301
  });
19302
+ /** One non-empty bucket of the timeline histogram. */
19303
+ var EventDensityBucketSchema = object({
19304
+ bucketStart: number(),
19305
+ motion: number().int(),
19306
+ object: number().int(),
19307
+ audio: number().int()
19308
+ });
19309
+ /**
19310
+ * One camera's row in a `getEventDensityBatch` answer.
19311
+ *
19312
+ * TWO facts, and the timeline draws them differently:
19313
+ *
19314
+ * - `read: 'read'` — the histogram for this camera. `buckets: []` is a real
19315
+ * claim: read, and nothing happened in the window.
19316
+ * - `read: 'unreadable'` — nobody could answer for this camera. `buckets` is
19317
+ * empty and that emptiness means NOTHING; the timeline must render unknown,
19318
+ * not quiet.
19319
+ *
19320
+ * Fanned out per camera the difference was free — one query rejected while the
19321
+ * others resolved — and collapsing to a batch is the exact moment it is lost
19322
+ * silently. Every requested id gets a row: a caller that asked for twelve and
19323
+ * got eleven cannot tell which one is missing, or that any is.
19324
+ */
19325
+ var EventDensityForDeviceSchema = object({
19326
+ deviceId: number(),
19327
+ read: _enum(["read", "unreadable"]),
19328
+ buckets: array(EventDensityBucketSchema).readonly()
19329
+ });
19272
19330
  object({
19273
19331
  trackId: string$2(),
19274
19332
  className: string$2(),
@@ -19579,12 +19637,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19579
19637
  since: number(),
19580
19638
  until: number(),
19581
19639
  bucketMs: number().int().positive()
19582
- }), array(object({
19583
- bucketStart: number(),
19584
- motion: number().int(),
19585
- object: number().int(),
19586
- audio: number().int()
19587
- })).readonly()), method(object({
19640
+ }), array(EventDensityBucketSchema).readonly()), method(object({
19641
+ deviceIds: array(number()).min(1).max(200),
19642
+ since: number(),
19643
+ until: number(),
19644
+ bucketMs: number().int().positive()
19645
+ }), array(EventDensityForDeviceSchema).readonly()), method(object({
19588
19646
  deviceId: number(),
19589
19647
  cutoffMs: number()
19590
19648
  }), object({
@@ -28626,6 +28684,39 @@ var RecordingDaysSchema = object({
28626
28684
  /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
28627
28685
  days: array(number())
28628
28686
  });
28687
+ /**
28688
+ * One camera's row in a `getAvailabilityBatch` answer.
28689
+ *
28690
+ * `ranges` is EXACTLY what `getAvailability` returns for that camera — the
28691
+ * batch collapses the transport, not the work — plus the one thing the singular
28692
+ * method never had to say:
28693
+ *
28694
+ * - `read: 'read'` — answered. `ranges: []` means "read, and this camera has
28695
+ * no footage in the window", which is a real claim.
28696
+ * - `read: 'unreadable'` — the recorder could not answer for this camera (its
28697
+ * calendar rung threw, its location is unmounted). `ranges` is empty and
28698
+ * that emptiness means NOTHING.
28699
+ *
28700
+ * A timeline that renders the second as the first tells an operator there is no
28701
+ * footage when the truth is that nobody looked.
28702
+ */
28703
+ var RecordingAvailabilityForDeviceSchema = object({
28704
+ deviceId: number(),
28705
+ read: _enum(["read", "unreadable"]),
28706
+ ranges: array(RecordingRangeSchema).readonly()
28707
+ });
28708
+ /**
28709
+ * One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
28710
+ * {@link RecordingAvailabilityForDeviceSchema}: `days: []` on a `'read'` row is
28711
+ * "no footage in this month", `read: 'unreadable'` is "nobody could look", and
28712
+ * the date-picker's day dots must not spell them the same.
28713
+ */
28714
+ var RecordingDaysForDeviceSchema = object({
28715
+ deviceId: number(),
28716
+ read: _enum(["read", "unreadable"]),
28717
+ /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
28718
+ days: array(number()).readonly()
28719
+ });
28629
28720
  var RecordingManifestSchema = object({
28630
28721
  deviceId: number(),
28631
28722
  /** Local filesystem path to the master playlist; null when no recording exists for the requested range. */
@@ -28860,6 +28951,13 @@ method(object({
28860
28951
  }), RecordingAvailabilitySchema, {
28861
28952
  kind: "query",
28862
28953
  auth: "protected"
28954
+ }), method(object({
28955
+ deviceIds: array(number()).min(1).max(200),
28956
+ fromMs: number(),
28957
+ toMs: number()
28958
+ }), array(RecordingAvailabilityForDeviceSchema).readonly(), {
28959
+ kind: "query",
28960
+ auth: "protected"
28863
28961
  }), method(object({
28864
28962
  deviceId: number(),
28865
28963
  fromMs: number(),
@@ -28868,6 +28966,14 @@ method(object({
28868
28966
  }), RecordingDaysSchema, {
28869
28967
  kind: "query",
28870
28968
  auth: "protected"
28969
+ }), method(object({
28970
+ deviceIds: array(number()).min(1).max(200),
28971
+ fromMs: number(),
28972
+ toMs: number(),
28973
+ tzOffsetMinutes: number()
28974
+ }), array(RecordingDaysForDeviceSchema).readonly(), {
28975
+ kind: "query",
28976
+ auth: "protected"
28871
28977
  }), method(object({
28872
28978
  deviceId: number(),
28873
28979
  fromMs: number(),
@@ -34030,6 +34136,12 @@ Object.freeze({
34030
34136
  addonId: null,
34031
34137
  access: "view"
34032
34138
  },
34139
+ "deviceManager.migrateDevice": {
34140
+ capName: "device-manager",
34141
+ capScope: "system",
34142
+ addonId: null,
34143
+ access: "create"
34144
+ },
34033
34145
  "deviceManager.persistConfig": {
34034
34146
  capName: "device-manager",
34035
34147
  capScope: "system",
@@ -35764,6 +35876,12 @@ Object.freeze({
35764
35876
  addonId: null,
35765
35877
  access: "view"
35766
35878
  },
35879
+ "pipelineAnalytics.getEventDensityBatch": {
35880
+ capName: "pipeline-analytics",
35881
+ capScope: "device",
35882
+ addonId: null,
35883
+ access: "view"
35884
+ },
35767
35885
  "pipelineAnalytics.getEventMedia": {
35768
35886
  capName: "pipeline-analytics",
35769
35887
  capScope: "device",
@@ -36886,12 +37004,24 @@ Object.freeze({
36886
37004
  addonId: null,
36887
37005
  access: "view"
36888
37006
  },
37007
+ "recording.getAvailabilityBatch": {
37008
+ capName: "recording",
37009
+ capScope: "system",
37010
+ addonId: null,
37011
+ access: "view"
37012
+ },
36889
37013
  "recording.getDaysWithRecordings": {
36890
37014
  capName: "recording",
36891
37015
  capScope: "system",
36892
37016
  addonId: null,
36893
37017
  access: "view"
36894
37018
  },
37019
+ "recording.getDaysWithRecordingsBatch": {
37020
+ capName: "recording",
37021
+ capScope: "system",
37022
+ addonId: null,
37023
+ access: "view"
37024
+ },
36895
37025
  "recording.getDeviceConfig": {
36896
37026
  capName: "recording",
36897
37027
  capScope: "system",
@@ -39496,6 +39626,11 @@ Object.freeze({
39496
39626
  form: "single",
39497
39627
  optional: false
39498
39628
  }],
39629
+ "pipelineAnalytics.getEventDensityBatch": [{
39630
+ name: "deviceIds",
39631
+ form: "array",
39632
+ optional: false
39633
+ }],
39499
39634
  "pipelineAnalytics.getEventMedia": [{
39500
39635
  name: "deviceId",
39501
39636
  form: "single",
@@ -39911,11 +40046,21 @@ Object.freeze({
39911
40046
  form: "single",
39912
40047
  optional: false
39913
40048
  }],
40049
+ "recording.getAvailabilityBatch": [{
40050
+ name: "deviceIds",
40051
+ form: "array",
40052
+ optional: false
40053
+ }],
39914
40054
  "recording.getDaysWithRecordings": [{
39915
40055
  name: "deviceId",
39916
40056
  form: "single",
39917
40057
  optional: false
39918
40058
  }],
40059
+ "recording.getDaysWithRecordingsBatch": [{
40060
+ name: "deviceIds",
40061
+ form: "array",
40062
+ optional: false
40063
+ }],
39919
40064
  "recording.getDeviceConfig": [{
39920
40065
  name: "deviceId",
39921
40066
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.2.50",
3
+ "version": "0.2.52",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",