@camstack/addon-export-hap 1.2.134 → 1.2.140

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.
@@ -25470,6 +25470,23 @@ var videoclipsCapability = {
25470
25470
  deviceId: number(),
25471
25471
  clipId: string().min(1),
25472
25472
  /**
25473
+ * WHICH provider holds the bytes — the `addonId` a
25474
+ * {@link ClipSourceSchema} row carries. **Required**, for the reason
25475
+ * `listClips` states one method above and for a second one this method
25476
+ * learned the hard way.
25477
+ *
25478
+ * The caller is the stream broker, and the broker lives in
25479
+ * `addon-pipeline` — the same addon that registers the RECORDER's
25480
+ * videoclips provider. With no provider named, the in-process
25481
+ * resolution handed it its own sibling, which implements this method
25482
+ * solely to explain that an analytics visit has no file behind it. So
25483
+ * every onboard clip answered with that sentence, a flash of video and
25484
+ * nothing more, while the SAME call from outside the addon reached the
25485
+ * right provider and returned the bytes (measured on 3628,
25486
+ * 2026-09-21).
25487
+ */
25488
+ provider: string().min(1),
25489
+ /**
25473
25490
  * Which twin to fetch, on the mapping D549 15 already fixed:
25474
25491
  * `low | mid` → the sub file, `high` → the main twin. `auto` is not
25475
25492
  * accepted here for the same reason it is not accepted by
@@ -31233,7 +31250,7 @@ method(object({
31233
31250
  auth: "admin"
31234
31251
  }), method(object({ deviceId: number() }), RecordingConfigSchema, {
31235
31252
  kind: "query",
31236
- auth: "admin"
31253
+ auth: "protected"
31237
31254
  }), method(object({
31238
31255
  deviceId: number(),
31239
31256
  profile: string(),
@@ -33480,6 +33497,12 @@ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeate
33480
33497
  new Set(Object.values(DeviceType));
33481
33498
  DeviceFeature.BatteryOperated;
33482
33499
  Object.freeze({
33500
+ "accessories.getStatus": {
33501
+ capName: "accessories",
33502
+ capScope: "device",
33503
+ addonId: null,
33504
+ access: "view"
33505
+ },
33483
33506
  "accessories.setChildHidden": {
33484
33507
  capName: "accessories",
33485
33508
  capScope: "device",
@@ -33762,6 +33785,12 @@ Object.freeze({
33762
33785
  addonId: null,
33763
33786
  access: "view"
33764
33787
  },
33788
+ "airQualitySensor.getStatus": {
33789
+ capName: "air-quality-sensor",
33790
+ capScope: "device",
33791
+ addonId: null,
33792
+ access: "view"
33793
+ },
33765
33794
  "alarmPanel.arm": {
33766
33795
  capName: "alarm-panel",
33767
33796
  capScope: "device",
@@ -33774,6 +33803,12 @@ Object.freeze({
33774
33803
  addonId: null,
33775
33804
  access: "create"
33776
33805
  },
33806
+ "alarmPanel.getStatus": {
33807
+ capName: "alarm-panel",
33808
+ capScope: "device",
33809
+ addonId: null,
33810
+ access: "view"
33811
+ },
33777
33812
  "alarmPanel.trigger": {
33778
33813
  capName: "alarm-panel",
33779
33814
  capScope: "device",
@@ -33822,6 +33857,30 @@ Object.freeze({
33822
33857
  addonId: null,
33823
33858
  access: "create"
33824
33859
  },
33860
+ "ambientLightSensor.getStatus": {
33861
+ capName: "ambient-light-sensor",
33862
+ capScope: "device",
33863
+ addonId: null,
33864
+ access: "view"
33865
+ },
33866
+ "audioAnalysis.applyDeviceSettingsPatch": {
33867
+ capName: "audio-analysis",
33868
+ capScope: "device",
33869
+ addonId: null,
33870
+ access: "create"
33871
+ },
33872
+ "audioAnalysis.getDeviceLiveContribution": {
33873
+ capName: "audio-analysis",
33874
+ capScope: "device",
33875
+ addonId: null,
33876
+ access: "view"
33877
+ },
33878
+ "audioAnalysis.getDeviceSettingsContribution": {
33879
+ capName: "audio-analysis",
33880
+ capScope: "device",
33881
+ addonId: null,
33882
+ access: "view"
33883
+ },
33825
33884
  "audioAnalysis.resolveDeviceSettings": {
33826
33885
  capName: "audio-analysis",
33827
33886
  capScope: "device",
@@ -33984,6 +34043,12 @@ Object.freeze({
33984
34043
  addonId: null,
33985
34044
  access: "create"
33986
34045
  },
34046
+ "automationControl.getStatus": {
34047
+ capName: "automation-control",
34048
+ capScope: "device",
34049
+ addonId: null,
34050
+ access: "view"
34051
+ },
33987
34052
  "automationControl.trigger": {
33988
34053
  capName: "automation-control",
33989
34054
  capScope: "device",
@@ -34080,12 +34145,30 @@ Object.freeze({
34080
34145
  addonId: null,
34081
34146
  access: "create"
34082
34147
  },
34148
+ "battery.getStatus": {
34149
+ capName: "battery",
34150
+ capScope: "device",
34151
+ addonId: null,
34152
+ access: "view"
34153
+ },
34083
34154
  "battery.wakeForStream": {
34084
34155
  capName: "battery",
34085
34156
  capScope: "device",
34086
34157
  addonId: null,
34087
34158
  access: "create"
34088
34159
  },
34160
+ "binary.getStatus": {
34161
+ capName: "binary",
34162
+ capScope: "device",
34163
+ addonId: null,
34164
+ access: "view"
34165
+ },
34166
+ "brightness.getStatus": {
34167
+ capName: "brightness",
34168
+ capScope: "device",
34169
+ addonId: null,
34170
+ access: "view"
34171
+ },
34089
34172
  "brightness.setBrightness": {
34090
34173
  capName: "brightness",
34091
34174
  capScope: "device",
@@ -34200,6 +34283,30 @@ Object.freeze({
34200
34283
  addonId: null,
34201
34284
  access: "view"
34202
34285
  },
34286
+ "cameraCredentials.getStatus": {
34287
+ capName: "camera-credentials",
34288
+ capScope: "device",
34289
+ addonId: null,
34290
+ access: "view"
34291
+ },
34292
+ "cameraGridLayout.applyDeviceSettingsPatch": {
34293
+ capName: "camera-grid-layout",
34294
+ capScope: "device",
34295
+ addonId: null,
34296
+ access: "create"
34297
+ },
34298
+ "cameraGridLayout.getDeviceLiveContribution": {
34299
+ capName: "camera-grid-layout",
34300
+ capScope: "device",
34301
+ addonId: null,
34302
+ access: "view"
34303
+ },
34304
+ "cameraGridLayout.getDeviceSettingsContribution": {
34305
+ capName: "camera-grid-layout",
34306
+ capScope: "device",
34307
+ addonId: null,
34308
+ access: "view"
34309
+ },
34203
34310
  "cameraGridLayout.getLayout": {
34204
34311
  capName: "camera-grid-layout",
34205
34312
  capScope: "device",
@@ -34212,6 +34319,24 @@ Object.freeze({
34212
34319
  addonId: null,
34213
34320
  access: "create"
34214
34321
  },
34322
+ "cameraPipelineConfig.applyDeviceSettingsPatch": {
34323
+ capName: "camera-pipeline-config",
34324
+ capScope: "device",
34325
+ addonId: null,
34326
+ access: "create"
34327
+ },
34328
+ "cameraPipelineConfig.getDeviceLiveContribution": {
34329
+ capName: "camera-pipeline-config",
34330
+ capScope: "device",
34331
+ addonId: null,
34332
+ access: "view"
34333
+ },
34334
+ "cameraPipelineConfig.getDeviceSettingsContribution": {
34335
+ capName: "camera-pipeline-config",
34336
+ capScope: "device",
34337
+ addonId: null,
34338
+ access: "view"
34339
+ },
34215
34340
  "cameraStreams.getBrokerStreams": {
34216
34341
  capName: "camera-streams",
34217
34342
  capScope: "device",
@@ -34242,6 +34367,18 @@ Object.freeze({
34242
34367
  addonId: null,
34243
34368
  access: "view"
34244
34369
  },
34370
+ "carbonMonoxide.getStatus": {
34371
+ capName: "carbon-monoxide",
34372
+ capScope: "device",
34373
+ addonId: null,
34374
+ access: "view"
34375
+ },
34376
+ "climateControl.getStatus": {
34377
+ capName: "climate-control",
34378
+ capScope: "device",
34379
+ addonId: null,
34380
+ access: "view"
34381
+ },
34245
34382
  "climateControl.setFanMode": {
34246
34383
  capName: "climate-control",
34247
34384
  capScope: "device",
@@ -34290,6 +34427,12 @@ Object.freeze({
34290
34427
  addonId: null,
34291
34428
  access: "create"
34292
34429
  },
34430
+ "color.getStatus": {
34431
+ capName: "color",
34432
+ capScope: "device",
34433
+ addonId: null,
34434
+ access: "view"
34435
+ },
34293
34436
  "color.setColor": {
34294
34437
  capName: "color",
34295
34438
  capScope: "device",
@@ -34308,12 +34451,36 @@ Object.freeze({
34308
34451
  addonId: null,
34309
34452
  access: "create"
34310
34453
  },
34454
+ "connectivity.getStatus": {
34455
+ capName: "connectivity",
34456
+ capScope: "device",
34457
+ addonId: null,
34458
+ access: "view"
34459
+ },
34460
+ "consumables.getStatus": {
34461
+ capName: "consumables",
34462
+ capScope: "device",
34463
+ addonId: null,
34464
+ access: "view"
34465
+ },
34311
34466
  "consumables.reset": {
34312
34467
  capName: "consumables",
34313
34468
  capScope: "device",
34314
34469
  addonId: null,
34315
34470
  access: "delete"
34316
34471
  },
34472
+ "contact.getStatus": {
34473
+ capName: "contact",
34474
+ capScope: "device",
34475
+ addonId: null,
34476
+ access: "view"
34477
+ },
34478
+ "control.getStatus": {
34479
+ capName: "control",
34480
+ capScope: "device",
34481
+ addonId: null,
34482
+ access: "view"
34483
+ },
34317
34484
  "control.setValue": {
34318
34485
  capName: "control",
34319
34486
  capScope: "device",
@@ -34380,6 +34547,12 @@ Object.freeze({
34380
34547
  addonId: null,
34381
34548
  access: "create"
34382
34549
  },
34550
+ "cover.getStatus": {
34551
+ capName: "cover",
34552
+ capScope: "device",
34553
+ addonId: null,
34554
+ access: "view"
34555
+ },
34383
34556
  "cover.open": {
34384
34557
  capName: "cover",
34385
34558
  capScope: "device",
@@ -34512,6 +34685,12 @@ Object.freeze({
34512
34685
  addonId: null,
34513
34686
  access: "view"
34514
34687
  },
34688
+ "dayNight.getStatus": {
34689
+ capName: "day-night",
34690
+ capScope: "device",
34691
+ addonId: null,
34692
+ access: "view"
34693
+ },
34515
34694
  "dayNight.setSettings": {
34516
34695
  capName: "day-night",
34517
34696
  capScope: "device",
@@ -34602,6 +34781,24 @@ Object.freeze({
34602
34781
  addonId: null,
34603
34782
  access: "view"
34604
34783
  },
34784
+ "detectionPipeline.applyDeviceSettingsPatch": {
34785
+ capName: "detection-pipeline",
34786
+ capScope: "device",
34787
+ addonId: null,
34788
+ access: "create"
34789
+ },
34790
+ "detectionPipeline.getDeviceLiveContribution": {
34791
+ capName: "detection-pipeline",
34792
+ capScope: "device",
34793
+ addonId: null,
34794
+ access: "view"
34795
+ },
34796
+ "detectionPipeline.getDeviceSettingsContribution": {
34797
+ capName: "detection-pipeline",
34798
+ capScope: "device",
34799
+ addonId: null,
34800
+ access: "view"
34801
+ },
34605
34802
  "deviceAdoption.adopt": {
34606
34803
  capName: "device-adoption",
34607
34804
  capScope: "system",
@@ -34614,6 +34811,12 @@ Object.freeze({
34614
34811
  addonId: null,
34615
34812
  access: "view"
34616
34813
  },
34814
+ "deviceAdoption.getStatus": {
34815
+ capName: "device-adoption",
34816
+ capScope: "system",
34817
+ addonId: null,
34818
+ access: "view"
34819
+ },
34617
34820
  "deviceAdoption.listCandidateFilters": {
34618
34821
  capName: "device-adoption",
34619
34822
  capScope: "system",
@@ -34650,6 +34853,12 @@ Object.freeze({
34650
34853
  addonId: null,
34651
34854
  access: "create"
34652
34855
  },
34856
+ "deviceDiscovery.getStatus": {
34857
+ capName: "device-discovery",
34858
+ capScope: "device",
34859
+ addonId: null,
34860
+ access: "view"
34861
+ },
34653
34862
  "deviceDiscovery.listDiscovered": {
34654
34863
  capName: "device-discovery",
34655
34864
  capScope: "device",
@@ -34668,12 +34877,30 @@ Object.freeze({
34668
34877
  addonId: null,
34669
34878
  access: "create"
34670
34879
  },
34880
+ "deviceExport.applyDeviceSettingsPatch": {
34881
+ capName: "device-export",
34882
+ capScope: "system",
34883
+ addonId: null,
34884
+ access: "create"
34885
+ },
34671
34886
  "deviceExport.exposeDevice": {
34672
34887
  capName: "device-export",
34673
34888
  capScope: "system",
34674
34889
  addonId: null,
34675
34890
  access: "create"
34676
34891
  },
34892
+ "deviceExport.getDeviceLiveContribution": {
34893
+ capName: "device-export",
34894
+ capScope: "system",
34895
+ addonId: null,
34896
+ access: "view"
34897
+ },
34898
+ "deviceExport.getDeviceSettingsContribution": {
34899
+ capName: "device-export",
34900
+ capScope: "system",
34901
+ addonId: null,
34902
+ access: "view"
34903
+ },
34677
34904
  "deviceExport.getStatus": {
34678
34905
  capName: "device-export",
34679
34906
  capScope: "system",
@@ -34770,6 +34997,12 @@ Object.freeze({
34770
34997
  addonId: null,
34771
34998
  access: "create"
34772
34999
  },
35000
+ "deviceManager.applyDeviceSettingsPatch": {
35001
+ capName: "device-manager",
35002
+ capScope: "system",
35003
+ addonId: null,
35004
+ access: "create"
35005
+ },
34773
35006
  "deviceManager.applyInitialMeta": {
34774
35007
  capName: "device-manager",
34775
35008
  capScope: "system",
@@ -34872,6 +35105,12 @@ Object.freeze({
34872
35105
  addonId: null,
34873
35106
  access: "view"
34874
35107
  },
35108
+ "deviceManager.getDeviceLiveContribution": {
35109
+ capName: "device-manager",
35110
+ capScope: "system",
35111
+ addonId: null,
35112
+ access: "view"
35113
+ },
34875
35114
  "deviceManager.getDeviceLiveInfoAggregate": {
34876
35115
  capName: "device-manager",
34877
35116
  capScope: "system",
@@ -34884,6 +35123,12 @@ Object.freeze({
34884
35123
  addonId: null,
34885
35124
  access: "view"
34886
35125
  },
35126
+ "deviceManager.getDeviceSettingsContribution": {
35127
+ capName: "device-manager",
35128
+ capScope: "system",
35129
+ addonId: null,
35130
+ access: "view"
35131
+ },
34887
35132
  "deviceManager.getDeviceStatusAggregate": {
34888
35133
  capName: "device-manager",
34889
35134
  capScope: "system",
@@ -35376,6 +35621,36 @@ Object.freeze({
35376
35621
  addonId: null,
35377
35622
  access: "create"
35378
35623
  },
35624
+ "deviceStatus.getStatus": {
35625
+ capName: "device-status",
35626
+ capScope: "device",
35627
+ addonId: null,
35628
+ access: "view"
35629
+ },
35630
+ "doorbell.applyDeviceSettingsPatch": {
35631
+ capName: "doorbell",
35632
+ capScope: "device",
35633
+ addonId: null,
35634
+ access: "create"
35635
+ },
35636
+ "doorbell.getDeviceLiveContribution": {
35637
+ capName: "doorbell",
35638
+ capScope: "device",
35639
+ addonId: null,
35640
+ access: "view"
35641
+ },
35642
+ "doorbell.getDeviceSettingsContribution": {
35643
+ capName: "doorbell",
35644
+ capScope: "device",
35645
+ addonId: null,
35646
+ access: "view"
35647
+ },
35648
+ "doorbell.getStatus": {
35649
+ capName: "doorbell",
35650
+ capScope: "device",
35651
+ addonId: null,
35652
+ access: "view"
35653
+ },
35379
35654
  "embeddingEncoder.encode": {
35380
35655
  capName: "embedding-encoder",
35381
35656
  capScope: "system",
@@ -35394,6 +35669,18 @@ Object.freeze({
35394
35669
  addonId: null,
35395
35670
  access: "view"
35396
35671
  },
35672
+ "enumSensor.getStatus": {
35673
+ capName: "enum-sensor",
35674
+ capScope: "device",
35675
+ addonId: null,
35676
+ access: "view"
35677
+ },
35678
+ "eventEmitter.getStatus": {
35679
+ capName: "event-emitter",
35680
+ capScope: "device",
35681
+ addonId: null,
35682
+ access: "view"
35683
+ },
35397
35684
  "events.getEventClipUrl": {
35398
35685
  capName: "events",
35399
35686
  capScope: "device",
@@ -35514,6 +35801,12 @@ Object.freeze({
35514
35801
  addonId: null,
35515
35802
  access: "view"
35516
35803
  },
35804
+ "fanControl.getStatus": {
35805
+ capName: "fan-control",
35806
+ capScope: "device",
35807
+ addonId: null,
35808
+ access: "view"
35809
+ },
35517
35810
  "fanControl.setDirection": {
35518
35811
  capName: "fan-control",
35519
35812
  capScope: "device",
@@ -35538,6 +35831,12 @@ Object.freeze({
35538
35831
  addonId: null,
35539
35832
  access: "create"
35540
35833
  },
35834
+ "featureProbe.getStatus": {
35835
+ capName: "feature-probe",
35836
+ capScope: "device",
35837
+ addonId: null,
35838
+ access: "view"
35839
+ },
35541
35840
  "filesystemBrowse.browse": {
35542
35841
  capName: "filesystem-browse",
35543
35842
  capScope: "system",
@@ -35556,6 +35855,24 @@ Object.freeze({
35556
35855
  addonId: null,
35557
35856
  access: "view"
35558
35857
  },
35858
+ "flood.getStatus": {
35859
+ capName: "flood",
35860
+ capScope: "device",
35861
+ addonId: null,
35862
+ access: "view"
35863
+ },
35864
+ "gas.getStatus": {
35865
+ capName: "gas",
35866
+ capScope: "device",
35867
+ addonId: null,
35868
+ access: "view"
35869
+ },
35870
+ "humidifier.getStatus": {
35871
+ capName: "humidifier",
35872
+ capScope: "device",
35873
+ addonId: null,
35874
+ access: "view"
35875
+ },
35559
35876
  "humidifier.setMode": {
35560
35877
  capName: "humidifier",
35561
35878
  capScope: "device",
@@ -35574,12 +35891,30 @@ Object.freeze({
35574
35891
  addonId: null,
35575
35892
  access: "create"
35576
35893
  },
35894
+ "humiditySensor.getStatus": {
35895
+ capName: "humidity-sensor",
35896
+ capScope: "device",
35897
+ addonId: null,
35898
+ access: "view"
35899
+ },
35900
+ "image.getStatus": {
35901
+ capName: "image",
35902
+ capScope: "device",
35903
+ addonId: null,
35904
+ access: "view"
35905
+ },
35577
35906
  "imageSettings.getOptions": {
35578
35907
  capName: "image-settings",
35579
35908
  capScope: "device",
35580
35909
  addonId: null,
35581
35910
  access: "view"
35582
35911
  },
35912
+ "imageSettings.getStatus": {
35913
+ capName: "image-settings",
35914
+ capScope: "device",
35915
+ addonId: null,
35916
+ access: "view"
35917
+ },
35583
35918
  "imageSettings.setSettings": {
35584
35919
  capName: "image-settings",
35585
35920
  capScope: "device",
@@ -35652,6 +35987,12 @@ Object.freeze({
35652
35987
  addonId: null,
35653
35988
  access: "create"
35654
35989
  },
35990
+ "intercom.getStatus": {
35991
+ capName: "intercom",
35992
+ capScope: "device",
35993
+ addonId: null,
35994
+ access: "view"
35995
+ },
35655
35996
  "intercom.handleAnswer": {
35656
35997
  capName: "intercom",
35657
35998
  capScope: "device",
@@ -35688,6 +36029,12 @@ Object.freeze({
35688
36029
  addonId: null,
35689
36030
  access: "create"
35690
36031
  },
36032
+ "lawnMowerControl.getStatus": {
36033
+ capName: "lawn-mower-control",
36034
+ capScope: "device",
36035
+ addonId: null,
36036
+ access: "view"
36037
+ },
35691
36038
  "lawnMowerControl.pause": {
35692
36039
  capName: "lawn-mower-control",
35693
36040
  capScope: "device",
@@ -35970,6 +36317,12 @@ Object.freeze({
35970
36317
  addonId: null,
35971
36318
  access: "create"
35972
36319
  },
36320
+ "lockControl.getStatus": {
36321
+ capName: "lock-control",
36322
+ capScope: "device",
36323
+ addonId: null,
36324
+ access: "view"
36325
+ },
35973
36326
  "lockControl.lock": {
35974
36327
  capName: "lock-control",
35975
36328
  capScope: "device",
@@ -36018,6 +36371,12 @@ Object.freeze({
36018
36371
  addonId: null,
36019
36372
  access: "view"
36020
36373
  },
36374
+ "mediaPlayer.getStatus": {
36375
+ capName: "media-player",
36376
+ capScope: "device",
36377
+ addonId: null,
36378
+ access: "view"
36379
+ },
36021
36380
  "mediaPlayer.next": {
36022
36381
  capName: "media-player",
36023
36382
  capScope: "device",
@@ -36210,6 +36569,12 @@ Object.freeze({
36210
36569
  addonId: null,
36211
36570
  access: "create"
36212
36571
  },
36572
+ "motion.getStatus": {
36573
+ capName: "motion",
36574
+ capScope: "device",
36575
+ addonId: null,
36576
+ access: "view"
36577
+ },
36213
36578
  "motion.isDetected": {
36214
36579
  capName: "motion",
36215
36580
  capScope: "device",
@@ -36222,6 +36587,24 @@ Object.freeze({
36222
36587
  addonId: null,
36223
36588
  access: "create"
36224
36589
  },
36590
+ "motionDetection.applyDeviceSettingsPatch": {
36591
+ capName: "motion-detection",
36592
+ capScope: "device",
36593
+ addonId: null,
36594
+ access: "create"
36595
+ },
36596
+ "motionDetection.getDeviceLiveContribution": {
36597
+ capName: "motion-detection",
36598
+ capScope: "device",
36599
+ addonId: null,
36600
+ access: "view"
36601
+ },
36602
+ "motionDetection.getDeviceSettingsContribution": {
36603
+ capName: "motion-detection",
36604
+ capScope: "device",
36605
+ addonId: null,
36606
+ access: "view"
36607
+ },
36225
36608
  "motionDetection.removeCamera": {
36226
36609
  capName: "motion-detection",
36227
36610
  capScope: "device",
@@ -36234,6 +36617,12 @@ Object.freeze({
36234
36617
  addonId: null,
36235
36618
  access: "delete"
36236
36619
  },
36620
+ "motionTrigger.getStatus": {
36621
+ capName: "motion-trigger",
36622
+ capScope: "device",
36623
+ addonId: null,
36624
+ access: "view"
36625
+ },
36237
36626
  "motionTrigger.setMotionTrigger": {
36238
36627
  capName: "motion-trigger",
36239
36628
  capScope: "device",
@@ -36246,6 +36635,12 @@ Object.freeze({
36246
36635
  addonId: null,
36247
36636
  access: "view"
36248
36637
  },
36638
+ "motionZones.getStatus": {
36639
+ capName: "motion-zones",
36640
+ capScope: "device",
36641
+ addonId: null,
36642
+ access: "view"
36643
+ },
36249
36644
  "motionZones.setZone": {
36250
36645
  capName: "motion-zones",
36251
36646
  capScope: "device",
@@ -36306,6 +36701,12 @@ Object.freeze({
36306
36701
  addonId: null,
36307
36702
  access: "view"
36308
36703
  },
36704
+ "nativeObjectDetection.getStatus": {
36705
+ capName: "native-object-detection",
36706
+ capScope: "device",
36707
+ addonId: null,
36708
+ access: "view"
36709
+ },
36309
36710
  "nativeObjectDetection.setEnabled": {
36310
36711
  capName: "native-object-detection",
36311
36712
  capScope: "device",
@@ -36324,6 +36725,12 @@ Object.freeze({
36324
36725
  addonId: null,
36325
36726
  access: "view"
36326
36727
  },
36728
+ "navigation.getStatus": {
36729
+ capName: "navigation",
36730
+ capScope: "device",
36731
+ addonId: null,
36732
+ access: "view"
36733
+ },
36327
36734
  "navigation.goToPoint": {
36328
36735
  capName: "navigation",
36329
36736
  capScope: "device",
@@ -36408,6 +36815,12 @@ Object.freeze({
36408
36815
  addonId: null,
36409
36816
  access: "create"
36410
36817
  },
36818
+ "networkLink.getStatus": {
36819
+ capName: "network-link",
36820
+ capScope: "device",
36821
+ addonId: null,
36822
+ access: "view"
36823
+ },
36411
36824
  "networkQuality.getAllStats": {
36412
36825
  capName: "network-quality",
36413
36826
  capScope: "system",
@@ -36666,18 +37079,36 @@ Object.freeze({
36666
37079
  addonId: null,
36667
37080
  access: "create"
36668
37081
  },
37082
+ "notifier.getStatus": {
37083
+ capName: "notifier",
37084
+ capScope: "device",
37085
+ addonId: null,
37086
+ access: "view"
37087
+ },
36669
37088
  "notifier.send": {
36670
37089
  capName: "notifier",
36671
37090
  capScope: "device",
36672
37091
  addonId: null,
36673
37092
  access: "create"
36674
37093
  },
37094
+ "numericSensor.getStatus": {
37095
+ capName: "numeric-sensor",
37096
+ capScope: "device",
37097
+ addonId: null,
37098
+ access: "view"
37099
+ },
36675
37100
  "oauthIntegration.getDescriptor": {
36676
37101
  capName: "oauth-integration",
36677
37102
  capScope: "system",
36678
37103
  addonId: null,
36679
37104
  access: "view"
36680
37105
  },
37106
+ "osd.getStatus": {
37107
+ capName: "osd",
37108
+ capScope: "device",
37109
+ addonId: null,
37110
+ access: "view"
37111
+ },
36681
37112
  "osd.setOverlay": {
36682
37113
  capName: "osd",
36683
37114
  capScope: "device",
@@ -36750,6 +37181,12 @@ Object.freeze({
36750
37181
  addonId: null,
36751
37182
  access: "create"
36752
37183
  },
37184
+ "petFeeder.getStatus": {
37185
+ capName: "pet-feeder",
37186
+ capScope: "device",
37187
+ addonId: null,
37188
+ access: "view"
37189
+ },
36753
37190
  "petFeeder.markFoodReplenished": {
36754
37191
  capName: "pet-feeder",
36755
37192
  capScope: "device",
@@ -36792,6 +37229,12 @@ Object.freeze({
36792
37229
  addonId: null,
36793
37230
  access: "create"
36794
37231
  },
37232
+ "pipelineAnalytics.applyDeviceSettingsPatch": {
37233
+ capName: "pipeline-analytics",
37234
+ capScope: "device",
37235
+ addonId: null,
37236
+ access: "create"
37237
+ },
36795
37238
  "pipelineAnalytics.cancelRelocateMedia": {
36796
37239
  capName: "pipeline-analytics",
36797
37240
  capScope: "device",
@@ -36858,6 +37301,18 @@ Object.freeze({
36858
37301
  addonId: null,
36859
37302
  access: "view"
36860
37303
  },
37304
+ "pipelineAnalytics.getDeviceLiveContribution": {
37305
+ capName: "pipeline-analytics",
37306
+ capScope: "device",
37307
+ addonId: null,
37308
+ access: "view"
37309
+ },
37310
+ "pipelineAnalytics.getDeviceSettingsContribution": {
37311
+ capName: "pipeline-analytics",
37312
+ capScope: "device",
37313
+ addonId: null,
37314
+ access: "view"
37315
+ },
36861
37316
  "pipelineAnalytics.getEventDensity": {
36862
37317
  capName: "pipeline-analytics",
36863
37318
  capScope: "device",
@@ -37428,6 +37883,12 @@ Object.freeze({
37428
37883
  addonId: null,
37429
37884
  access: "view"
37430
37885
  },
37886
+ "pipelineOrchestrator.applyDeviceSettingsPatch": {
37887
+ capName: "pipeline-orchestrator",
37888
+ capScope: "system",
37889
+ addonId: null,
37890
+ access: "create"
37891
+ },
37431
37892
  "pipelineOrchestrator.assignAudio": {
37432
37893
  capName: "pipeline-orchestrator",
37433
37894
  capScope: "system",
@@ -37518,6 +37979,18 @@ Object.freeze({
37518
37979
  addonId: null,
37519
37980
  access: "view"
37520
37981
  },
37982
+ "pipelineOrchestrator.getDeviceLiveContribution": {
37983
+ capName: "pipeline-orchestrator",
37984
+ capScope: "system",
37985
+ addonId: null,
37986
+ access: "view"
37987
+ },
37988
+ "pipelineOrchestrator.getDeviceSettingsContribution": {
37989
+ capName: "pipeline-orchestrator",
37990
+ capScope: "system",
37991
+ addonId: null,
37992
+ access: "view"
37993
+ },
37521
37994
  "pipelineOrchestrator.getGlobalMetrics": {
37522
37995
  capName: "pipeline-orchestrator",
37523
37996
  capScope: "system",
@@ -37926,12 +38399,36 @@ Object.freeze({
37926
38399
  addonId: null,
37927
38400
  access: "view"
37928
38401
  },
38402
+ "powerMeter.getStatus": {
38403
+ capName: "power-meter",
38404
+ capScope: "device",
38405
+ addonId: null,
38406
+ access: "view"
38407
+ },
38408
+ "presence.getStatus": {
38409
+ capName: "presence",
38410
+ capScope: "device",
38411
+ addonId: null,
38412
+ access: "view"
38413
+ },
38414
+ "pressureSensor.getStatus": {
38415
+ capName: "pressure-sensor",
38416
+ capScope: "device",
38417
+ addonId: null,
38418
+ access: "view"
38419
+ },
37929
38420
  "privacyMask.getOptions": {
37930
38421
  capName: "privacy-mask",
37931
38422
  capScope: "device",
37932
38423
  addonId: null,
37933
38424
  access: "view"
37934
38425
  },
38426
+ "privacyMask.getStatus": {
38427
+ capName: "privacy-mask",
38428
+ capScope: "device",
38429
+ addonId: null,
38430
+ access: "view"
38431
+ },
37935
38432
  "privacyMask.setAudioEnabled": {
37936
38433
  capName: "privacy-mask",
37937
38434
  capScope: "device",
@@ -37992,6 +38489,12 @@ Object.freeze({
37992
38489
  addonId: null,
37993
38490
  access: "view"
37994
38491
  },
38492
+ "ptz.getStatus": {
38493
+ capName: "ptz",
38494
+ capScope: "device",
38495
+ addonId: null,
38496
+ access: "view"
38497
+ },
37995
38498
  "ptz.goHome": {
37996
38499
  capName: "ptz",
37997
38500
  capScope: "device",
@@ -38070,6 +38573,12 @@ Object.freeze({
38070
38573
  addonId: null,
38071
38574
  access: "create"
38072
38575
  },
38576
+ "recording.applyDeviceSettingsPatch": {
38577
+ capName: "recording",
38578
+ capScope: "system",
38579
+ addonId: null,
38580
+ access: "create"
38581
+ },
38073
38582
  "recording.cancelRelocateJob": {
38074
38583
  capName: "recording",
38075
38584
  capScope: "system",
@@ -38118,6 +38627,18 @@ Object.freeze({
38118
38627
  addonId: null,
38119
38628
  access: "view"
38120
38629
  },
38630
+ "recording.getDeviceLiveContribution": {
38631
+ capName: "recording",
38632
+ capScope: "system",
38633
+ addonId: null,
38634
+ access: "view"
38635
+ },
38636
+ "recording.getDeviceSettingsContribution": {
38637
+ capName: "recording",
38638
+ capScope: "system",
38639
+ addonId: null,
38640
+ access: "view"
38641
+ },
38121
38642
  "recording.getPlacement": {
38122
38643
  capName: "recording",
38123
38644
  capScope: "system",
@@ -38136,6 +38657,12 @@ Object.freeze({
38136
38657
  addonId: null,
38137
38658
  access: "view"
38138
38659
  },
38660
+ "recording.getStatus": {
38661
+ capName: "recording",
38662
+ capScope: "system",
38663
+ addonId: null,
38664
+ access: "view"
38665
+ },
38139
38666
  "recording.getStorageMigrationMoveStatus": {
38140
38667
  capName: "recording",
38141
38668
  capScope: "system",
@@ -38340,6 +38867,12 @@ Object.freeze({
38340
38867
  addonId: null,
38341
38868
  access: "delete"
38342
38869
  },
38870
+ "sceneMonitor.getStatus": {
38871
+ capName: "scene-monitor",
38872
+ capScope: "device",
38873
+ addonId: null,
38874
+ access: "view"
38875
+ },
38343
38876
  "sceneMonitor.listScenes": {
38344
38877
  capName: "scene-monitor",
38345
38878
  capScope: "device",
@@ -38370,6 +38903,12 @@ Object.freeze({
38370
38903
  addonId: null,
38371
38904
  access: "create"
38372
38905
  },
38906
+ "scriptRunner.getStatus": {
38907
+ capName: "script-runner",
38908
+ capScope: "device",
38909
+ addonId: null,
38910
+ access: "view"
38911
+ },
38373
38912
  "scriptRunner.run": {
38374
38913
  capName: "script-runner",
38375
38914
  capScope: "device",
@@ -38502,6 +39041,12 @@ Object.freeze({
38502
39041
  addonId: null,
38503
39042
  access: "create"
38504
39043
  },
39044
+ "smoke.getStatus": {
39045
+ capName: "smoke",
39046
+ capScope: "device",
39047
+ addonId: null,
39048
+ access: "view"
39049
+ },
38505
39050
  "smtpProvider.getStatus": {
38506
39051
  capName: "smtp-provider",
38507
39052
  capScope: "system",
@@ -38520,12 +39065,30 @@ Object.freeze({
38520
39065
  addonId: null,
38521
39066
  access: "view"
38522
39067
  },
39068
+ "snapshot.applyDeviceSettingsPatch": {
39069
+ capName: "snapshot",
39070
+ capScope: "device",
39071
+ addonId: null,
39072
+ access: "create"
39073
+ },
38523
39074
  "snapshot.getDebugState": {
38524
39075
  capName: "snapshot",
38525
39076
  capScope: "device",
38526
39077
  addonId: null,
38527
39078
  access: "view"
38528
39079
  },
39080
+ "snapshot.getDeviceLiveContribution": {
39081
+ capName: "snapshot",
39082
+ capScope: "device",
39083
+ addonId: null,
39084
+ access: "view"
39085
+ },
39086
+ "snapshot.getDeviceSettingsContribution": {
39087
+ capName: "snapshot",
39088
+ capScope: "device",
39089
+ addonId: null,
39090
+ access: "view"
39091
+ },
38529
39092
  "snapshot.getSnapshot": {
38530
39093
  capName: "snapshot",
38531
39094
  capScope: "device",
@@ -38544,6 +39107,12 @@ Object.freeze({
38544
39107
  addonId: null,
38545
39108
  access: "view"
38546
39109
  },
39110
+ "snapshot.getStatus": {
39111
+ capName: "snapshot",
39112
+ capScope: "device",
39113
+ addonId: null,
39114
+ access: "view"
39115
+ },
38547
39116
  "snapshot.invalidateCache": {
38548
39117
  capName: "snapshot",
38549
39118
  capScope: "device",
@@ -38880,6 +39449,12 @@ Object.freeze({
38880
39449
  addonId: null,
38881
39450
  access: "create"
38882
39451
  },
39452
+ "streamBroker.applyDeviceSettingsPatch": {
39453
+ capName: "stream-broker",
39454
+ capScope: "system",
39455
+ addonId: null,
39456
+ access: "create"
39457
+ },
38883
39458
  "streamBroker.assignProfile": {
38884
39459
  capName: "stream-broker",
38885
39460
  capScope: "system",
@@ -38916,6 +39491,18 @@ Object.freeze({
38916
39491
  addonId: null,
38917
39492
  access: "view"
38918
39493
  },
39494
+ "streamBroker.getDeviceLiveContribution": {
39495
+ capName: "stream-broker",
39496
+ capScope: "system",
39497
+ addonId: null,
39498
+ access: "view"
39499
+ },
39500
+ "streamBroker.getDeviceSettingsContribution": {
39501
+ capName: "stream-broker",
39502
+ capScope: "system",
39503
+ addonId: null,
39504
+ access: "view"
39505
+ },
38919
39506
  "streamBroker.getPreBufferInfo": {
38920
39507
  capName: "stream-broker",
38921
39508
  capScope: "system",
@@ -39114,6 +39701,12 @@ Object.freeze({
39114
39701
  addonId: null,
39115
39702
  access: "view"
39116
39703
  },
39704
+ "streamParams.getStatus": {
39705
+ capName: "stream-params",
39706
+ capScope: "device",
39707
+ addonId: null,
39708
+ access: "view"
39709
+ },
39117
39710
  "streamParams.setProfile": {
39118
39711
  capName: "stream-params",
39119
39712
  capScope: "device",
@@ -39126,6 +39719,12 @@ Object.freeze({
39126
39719
  addonId: null,
39127
39720
  access: "view"
39128
39721
  },
39722
+ "switch.getStatus": {
39723
+ capName: "switch",
39724
+ capScope: "device",
39725
+ addonId: null,
39726
+ access: "view"
39727
+ },
39129
39728
  "switch.setState": {
39130
39729
  capName: "switch",
39131
39730
  capScope: "device",
@@ -39222,6 +39821,18 @@ Object.freeze({
39222
39821
  addonId: null,
39223
39822
  access: "create"
39224
39823
  },
39824
+ "tamper.getStatus": {
39825
+ capName: "tamper",
39826
+ capScope: "device",
39827
+ addonId: null,
39828
+ access: "view"
39829
+ },
39830
+ "temperatureSensor.getStatus": {
39831
+ capName: "temperature-sensor",
39832
+ capScope: "device",
39833
+ addonId: null,
39834
+ access: "view"
39835
+ },
39225
39836
  "terminalSession.adoptLegacyMonitor": {
39226
39837
  capName: "terminal-session",
39227
39838
  capScope: "system",
@@ -39318,6 +39929,12 @@ Object.freeze({
39318
39929
  addonId: null,
39319
39930
  access: "view"
39320
39931
  },
39932
+ "update.getStatus": {
39933
+ capName: "update",
39934
+ capScope: "device",
39935
+ addonId: null,
39936
+ access: "view"
39937
+ },
39321
39938
  "update.installUpdate": {
39322
39939
  capName: "update",
39323
39940
  capScope: "device",
@@ -39540,6 +40157,12 @@ Object.freeze({
39540
40157
  addonId: null,
39541
40158
  access: "create"
39542
40159
  },
40160
+ "vacuumControl.getStatus": {
40161
+ capName: "vacuum-control",
40162
+ capScope: "device",
40163
+ addonId: null,
40164
+ access: "view"
40165
+ },
39543
40166
  "vacuumControl.locate": {
39544
40167
  capName: "vacuum-control",
39545
40168
  capScope: "device",
@@ -39582,6 +40205,12 @@ Object.freeze({
39582
40205
  addonId: null,
39583
40206
  access: "create"
39584
40207
  },
40208
+ "valve.getStatus": {
40209
+ capName: "valve",
40210
+ capScope: "device",
40211
+ addonId: null,
40212
+ access: "view"
40213
+ },
39585
40214
  "valve.open": {
39586
40215
  capName: "valve",
39587
40216
  capScope: "device",
@@ -39654,6 +40283,12 @@ Object.freeze({
39654
40283
  addonId: null,
39655
40284
  access: "create"
39656
40285
  },
40286
+ "vibration.getStatus": {
40287
+ capName: "vibration",
40288
+ capScope: "device",
40289
+ addonId: null,
40290
+ access: "view"
40291
+ },
39657
40292
  "videoclips.getClipPlayback": {
39658
40293
  capName: "videoclips",
39659
40294
  capScope: "device",
@@ -39690,6 +40325,12 @@ Object.freeze({
39690
40325
  addonId: null,
39691
40326
  access: "view"
39692
40327
  },
40328
+ "waterHeater.getStatus": {
40329
+ capName: "water-heater",
40330
+ capScope: "device",
40331
+ addonId: null,
40332
+ access: "view"
40333
+ },
39693
40334
  "waterHeater.setAway": {
39694
40335
  capName: "water-heater",
39695
40336
  capScope: "device",
@@ -39708,6 +40349,12 @@ Object.freeze({
39708
40349
  addonId: null,
39709
40350
  access: "create"
39710
40351
  },
40352
+ "weather.getStatus": {
40353
+ capName: "weather",
40354
+ capScope: "device",
40355
+ addonId: null,
40356
+ access: "view"
40357
+ },
39711
40358
  "webrtcSession.addIceCandidate": {
39712
40359
  capName: "webrtc-session",
39713
40360
  capScope: "device",
@@ -90670,6 +91317,153 @@ function createClipThumbnailMinter(input) {
90670
91317
  };
90671
91318
  }
90672
91319
  //#endregion
91320
+ //#region src/hksv/recording-active.ts
91321
+ /**
91322
+ * `true`/`false` as HomeKit holds it, or `null` when this accessory does not
91323
+ * offer recording at all.
91324
+ */
91325
+ function readHomekitRecordingActive(accessories) {
91326
+ for (const accessory of accessories) {
91327
+ const service = accessory.getService(import_dist.Service.CameraRecordingManagement);
91328
+ if (service === void 0) continue;
91329
+ const value = service.getCharacteristic(import_dist.Characteristic.Active).value;
91330
+ if (typeof value === "number") return value === 1;
91331
+ if (typeof value === "boolean") return value;
91332
+ return null;
91333
+ }
91334
+ return null;
91335
+ }
91336
+ /**
91337
+ * One sentence for the settings page: what HomeKit says, or why it was never
91338
+ * asked.
91339
+ *
91340
+ * The cost stays in the text even though the switch is gone. It is still the
91341
+ * operator's to understand — a camera recording here runs a permanent ffmpeg —
91342
+ * and a status line that reported only the state would have quietly dropped
91343
+ * the one number that decides whether to turn it on.
91344
+ */
91345
+ function describeHomekitRecording(active, lastBuild) {
91346
+ if (active === true) return "HomeKit is recording this camera. Turn it off in the Home app (Camera settings → “Stream”), not here — iOS owns this switch and keeps it across reboots. While it is on, this camera runs a permanent prebuffer (~0.7% of one CPU core on the copy branch).";
91347
+ if (active === false) return "This camera offers recording and HomeKit has it off. Turn it on in the Home app (Camera settings → “Stream and Allow Recording”); it needs iCloud+ and a home hub. Nothing runs here until you do.";
91348
+ if (lastBuild !== null && !lastBuild.advertised) return `Recording is not offered for this camera: ${lastBuild.detail ?? "the accessory could not advertise it"}.`;
91349
+ return "Not known yet — this camera’s accessory has not published its recording service in this process. It appears once the accessory is built.";
91350
+ }
91351
+ //#endregion
91352
+ //#region src/hksv/recording-source.ts
91353
+ /** The tallest frame the recording path will hold in its prebuffer. */
91354
+ var MAX_RECORDING_HEIGHT = 1080;
91355
+ /** HKSV takes H.264 only — AAC audio and H.264 video, no negotiation. */
91356
+ var RECORDABLE_CODEC = "h264";
91357
+ /**
91358
+ * Pick the slot the recording child pulls.
91359
+ *
91360
+ * Deliberately NOT `pickPreferredRtspEntry`: that picker resolves the operator's
91361
+ * LIVE preference and, on `auto`, steers by the resolution iOS negotiated for a
91362
+ * live session — neither is a fact about recording, and on 615 it selects `mid`,
91363
+ * a 10 fps slot. Recording has one criterion, applied here and nowhere else:
91364
+ * the largest copyable frame that does not exceed {@link MAX_RECORDING_HEIGHT}.
91365
+ */
91366
+ function pickRecordingSource(entries, options = {}) {
91367
+ const enabled = entries.filter((e) => e.enabled);
91368
+ if (enabled.length === 0) return {
91369
+ ok: false,
91370
+ refusal: "no-enabled-stream"
91371
+ };
91372
+ const h264 = enabled.filter((e) => normaliseCodec(e.codec) === RECORDABLE_CODEC);
91373
+ const pool = h264.length > 0 ? h264 : options.allowTranscode === true ? enabled : [];
91374
+ if (pool.length === 0) return {
91375
+ ok: false,
91376
+ refusal: "no-h264-stream"
91377
+ };
91378
+ const transcode = h264.length === 0;
91379
+ const sized = pool.filter(hasUsableResolution);
91380
+ if (sized.length === 0) return {
91381
+ ok: false,
91382
+ refusal: "no-resolution"
91383
+ };
91384
+ const withinCeiling = sized.filter((e) => height(e) <= MAX_RECORDING_HEIGHT);
91385
+ const within = withinCeiling.length > 0;
91386
+ const best = [...within ? withinCeiling : sized].sort((a, b) => within ? height(b) - height(a) : height(a) - height(b))[0];
91387
+ if (best === void 0 || best.resolution === void 0) return {
91388
+ ok: false,
91389
+ refusal: "no-resolution"
91390
+ };
91391
+ return {
91392
+ ok: true,
91393
+ source: {
91394
+ brokerId: best.brokerId,
91395
+ profile: best.profile ?? best.brokerId,
91396
+ transcode,
91397
+ url: best.url,
91398
+ mutedUrl: best.mutedUrl,
91399
+ width: best.resolution.width,
91400
+ height: best.resolution.height
91401
+ }
91402
+ };
91403
+ }
91404
+ /** A one-line reason for the log — silence about a withdrawn service reads as a bug. */
91405
+ function refusalReason(refusal) {
91406
+ switch (refusal) {
91407
+ case "no-enabled-stream": return "the camera has no enabled RTSP profile";
91408
+ case "no-h264-stream": return "every enabled profile is H.265 — HKSV takes H.264 only, and a permanent transcode costs 128x a copy";
91409
+ case "no-resolution": return "no enabled profile declares a resolution, so nothing honest could be advertised";
91410
+ }
91411
+ }
91412
+ function height(entry) {
91413
+ return entry.resolution?.height ?? 0;
91414
+ }
91415
+ function hasUsableResolution(entry) {
91416
+ const r = entry.resolution;
91417
+ return r !== void 0 && r.width > 0 && r.height > 0;
91418
+ }
91419
+ /** Publishers spell H.265 four ways; the same normalisation the broker uses. */
91420
+ function normaliseCodec(codec) {
91421
+ return (codec ?? "").toLowerCase().replace(/[.\s-]/g, "");
91422
+ }
91423
+ //#endregion
91424
+ //#region src/hksv/recordability-token.ts
91425
+ /** No slot is recordable. A real answer, and a fingerprint-moving one. */
91426
+ var RECORDABILITY_NONE = "rec:none";
91427
+ /**
91428
+ * The slot HKSV would record from, as a fingerprint token.
91429
+ *
91430
+ * `null` when `entries` is null — the profiles could not be read, which is not
91431
+ * a claim about the camera.
91432
+ */
91433
+ function recordabilityToken(entries, options = {}) {
91434
+ if (entries === null) return null;
91435
+ const choice = pickRecordingSource(entries, options);
91436
+ return choice.ok ? `rec:${choice.source.profile}${choice.source.transcode ? "+x" : ""}` : RECORDABILITY_NONE;
91437
+ }
91438
+ var TOKEN_SUFFIX = /\|(rec:[^|]*)$/;
91439
+ /**
91440
+ * Attach the recording token to a device-shape fingerprint.
91441
+ *
91442
+ * On an unreadable read the persisted token is carried forward — but only onto
91443
+ * the base it was persisted against. If the device's SHAPE also changed, that
91444
+ * change is a rebuild in its own right and the token has nothing to say about
91445
+ * it; inventing one there would advertise a recording slot nobody read.
91446
+ */
91447
+ function withRecordabilityToken(input) {
91448
+ if (input.token !== null) return `${input.base}|${input.token}`;
91449
+ const carried = input.persisted?.match(TOKEN_SUFFIX);
91450
+ if (carried === null || carried === void 0) return input.base;
91451
+ return carried.input?.slice(0, carried.index) === input.base ? `${input.base}|${carried[1]}` : input.base;
91452
+ }
91453
+ /**
91454
+ * What this camera could record, as one fingerprint token.
91455
+ *
91456
+ * There used to be an `rec:off` here, for a per-camera switch of ours. That
91457
+ * switch is gone (D589): whether HomeKit RECORDS is HomeKit's own persisted
91458
+ * characteristic, it does not change the accessory's shape, and a rebuild is
91459
+ * about shape. What still belongs here is the CAPABILITY — which slot, and
91460
+ * whether serving it means running an encoder — because those are two
91461
+ * different accessories to build.
91462
+ */
91463
+ function recordabilityTokenFor(input) {
91464
+ return recordabilityToken(input.entries, { allowTranscode: input.allowTranscode !== false });
91465
+ }
91466
+ //#endregion
90673
91467
  //#region src/hksv/videoclips-plane.ts
90674
91468
  /**
90675
91469
  * The two data-plane routes behind the HomeKit clip source.
@@ -90932,8 +91726,9 @@ function createHksvClipPlanes(deps) {
90932
91726
  *
90933
91727
  * ## The source EXISTS because of HomeKit, and is not a switch
90934
91728
  *
90935
- * A camera has this source because it is exported to HomeKit AND HomeKit
90936
- * recording is on for it; turn either off and the row is gone (D550, D569 § 5).
91729
+ * A camera has this source because it is exported to HomeKit and HomeKit has
91730
+ * not said no to recording it; un-export it, or switch recording off in the
91731
+ * Home app, and the row is gone (D550, D569 § 5, D589).
90937
91732
  * There is deliberately no enable control of its own — a knob over somebody
90938
91733
  * else's decision is D62's defect. `keepClips` is NOT that gate either: it
90939
91734
  * governs the TEE, and the clips already kept remain the truth about this
@@ -90980,7 +91775,7 @@ function createHksvVideoclipsProvider(deps) {
90980
91775
  */
90981
91776
  async function measure(deviceId) {
90982
91777
  const facts = deps.describeCamera(deviceId);
90983
- if (!facts.exported || !facts.recording) return null;
91778
+ if (!facts.exported || facts.homekitRecording === false) return null;
90984
91779
  const catalog = await deps.readCatalog(deviceId);
90985
91780
  return {
90986
91781
  availability: availabilityFor(facts, catalog),
@@ -106335,6 +107130,72 @@ function buildRecordingOptions(input) {
106335
107130
  };
106336
107131
  }
106337
107132
  //#endregion
107133
+ //#region src/hksv/recording-invocation.ts
107134
+ /**
107135
+ * The resolution the accessory may advertise, which is the one it can deliver.
107136
+ *
107137
+ * A copy is advertised at its source size whatever that is — there is no
107138
+ * encoder to resize with, and advertising a size we cannot produce is the
107139
+ * "stream that does not match its own advertisement" defect in miniature.
107140
+ * A transcode above the ceiling is fitted down, because with an encoder in the
107141
+ * pipeline the 6.35 MB 4K fragment is a choice rather than a fact, and iOS
107142
+ * renders HKSV clips at 1080p regardless.
107143
+ */
107144
+ function recordingOutput(source) {
107145
+ if (!source.transcode || source.height <= 1080) return {
107146
+ width: source.width,
107147
+ height: source.height,
107148
+ scaled: false
107149
+ };
107150
+ return {
107151
+ width: Math.max(2, Math.round(source.width * MAX_RECORDING_HEIGHT / source.height / 2) * 2),
107152
+ height: MAX_RECORDING_HEIGHT,
107153
+ scaled: true
107154
+ };
107155
+ }
107156
+ /** The invocation, minus the sink and sidecar the child owns. */
107157
+ function buildRecordingInvocation(input) {
107158
+ const { source } = input;
107159
+ const audio = input.audioActive ? {
107160
+ kind: "encode",
107161
+ codec: "aac",
107162
+ bitrateKbps: 32,
107163
+ sampleRateHz: HKSV_AUDIO_SAMPLE_RATE_HZ,
107164
+ channels: 1
107165
+ } : { kind: "none" };
107166
+ const out = recordingOutput(source);
107167
+ const video = source.transcode ? {
107168
+ kind: "encode",
107169
+ encoder: "libx264",
107170
+ preset: "veryfast",
107171
+ profile: "main",
107172
+ level: "4.0",
107173
+ pixelFormat: "yuv420p",
107174
+ forceKeyFramesSeconds: Math.max(1, Math.round(input.fragmentMs / 1e3)),
107175
+ bf: 0,
107176
+ ...out.scaled ? { scale: {
107177
+ mode: "fit",
107178
+ width: out.width,
107179
+ height: out.height
107180
+ } } : {}
107181
+ } : { kind: "copy" };
107182
+ return {
107183
+ logLevel: "error",
107184
+ decodeHwAccel: source.transcode ? input.decodeHwAccel : null,
107185
+ input: {
107186
+ url: input.audioActive ? source.url : source.mutedUrl,
107187
+ rtspTransport: "tcp",
107188
+ ...source.transcode && input.decodeInputArgs !== void 0 && input.decodeInputArgs.length > 0 ? { extraArgs: input.decodeInputArgs } : {},
107189
+ analyzeDurationUs: 1e6,
107190
+ probeSizeBytes: 1e6
107191
+ },
107192
+ video,
107193
+ audio,
107194
+ threadCount: 0,
107195
+ outputArgs: []
107196
+ };
107197
+ }
107198
+ //#endregion
106338
107199
  //#region src/hksv/fragment-source.ts
106339
107200
  /**
106340
107201
  * How far back the prebuffer ring reaches.
@@ -106502,6 +107363,7 @@ var HksvFragmentSource = class {
106502
107363
  meta: {
106503
107364
  brokerId: this.input.source.brokerId,
106504
107365
  resolution: `${this.input.source.width}x${this.input.source.height}`,
107366
+ branch: this.input.source.transcode ? "transcode" : "copy",
106505
107367
  fragmentMs: this.input.fragmentMs,
106506
107368
  audioActive: this.audioActive,
106507
107369
  windowMs: PREBUFFER_WINDOW_MS
@@ -106566,27 +107428,13 @@ var HksvFragmentSource = class {
106566
107428
  * takes AAC only — which the same measurement priced at 0.4 % of a core.
106567
107429
  */
106568
107430
  buildInvocation() {
106569
- const audio = this.audioActive ? {
106570
- kind: "encode",
106571
- codec: "aac",
106572
- bitrateKbps: 32,
106573
- sampleRateHz: HKSV_AUDIO_SAMPLE_RATE_HZ,
106574
- channels: 1
106575
- } : { kind: "none" };
106576
- return {
106577
- logLevel: "error",
106578
- decodeHwAccel: null,
106579
- input: {
106580
- url: this.audioActive ? this.input.source.url : this.input.source.mutedUrl,
106581
- rtspTransport: "tcp",
106582
- analyzeDurationUs: 1e6,
106583
- probeSizeBytes: 1e6
106584
- },
106585
- video: { kind: "copy" },
106586
- audio,
106587
- threadCount: 0,
106588
- outputArgs: []
106589
- };
107431
+ return buildRecordingInvocation({
107432
+ source: this.input.source,
107433
+ audioActive: this.audioActive,
107434
+ fragmentMs: this.input.fragmentMs,
107435
+ decodeHwAccel: this.input.decodeHwAccel ?? null,
107436
+ ...this.input.decodeInputArgs === void 0 ? {} : { decodeInputArgs: this.input.decodeInputArgs }
107437
+ });
106590
107438
  }
106591
107439
  };
106592
107440
  //#endregion
@@ -106946,74 +107794,6 @@ var HksvRecordingDelegate = class {
106946
107794
  }
106947
107795
  };
106948
107796
  //#endregion
106949
- //#region src/hksv/recording-source.ts
106950
- /** The tallest frame the recording path will hold in its prebuffer. */
106951
- var MAX_RECORDING_HEIGHT = 1080;
106952
- /** HKSV takes H.264 only — AAC audio and H.264 video, no negotiation. */
106953
- var RECORDABLE_CODEC = "h264";
106954
- /**
106955
- * Pick the slot the recording child pulls.
106956
- *
106957
- * Deliberately NOT `pickPreferredRtspEntry`: that picker resolves the operator's
106958
- * LIVE preference and, on `auto`, steers by the resolution iOS negotiated for a
106959
- * live session — neither is a fact about recording, and on 615 it selects `mid`,
106960
- * a 10 fps slot. Recording has one criterion, applied here and nowhere else:
106961
- * the largest copyable frame that does not exceed {@link MAX_RECORDING_HEIGHT}.
106962
- */
106963
- function pickRecordingSource(entries) {
106964
- const enabled = entries.filter((e) => e.enabled);
106965
- if (enabled.length === 0) return {
106966
- ok: false,
106967
- refusal: "no-enabled-stream"
106968
- };
106969
- const h264 = enabled.filter((e) => normaliseCodec(e.codec) === RECORDABLE_CODEC);
106970
- if (h264.length === 0) return {
106971
- ok: false,
106972
- refusal: "no-h264-stream"
106973
- };
106974
- const sized = h264.filter(hasUsableResolution);
106975
- if (sized.length === 0) return {
106976
- ok: false,
106977
- refusal: "no-resolution"
106978
- };
106979
- const withinCeiling = sized.filter((e) => height(e) <= MAX_RECORDING_HEIGHT);
106980
- const best = [...withinCeiling.length > 0 ? withinCeiling : sized].sort((a, b) => withinCeiling.length > 0 ? height(b) - height(a) : height(a) - height(b))[0];
106981
- if (best === void 0 || best.resolution === void 0) return {
106982
- ok: false,
106983
- refusal: "no-resolution"
106984
- };
106985
- return {
106986
- ok: true,
106987
- source: {
106988
- brokerId: best.brokerId,
106989
- profile: best.profile ?? best.brokerId,
106990
- url: best.url,
106991
- mutedUrl: best.mutedUrl,
106992
- width: best.resolution.width,
106993
- height: best.resolution.height
106994
- }
106995
- };
106996
- }
106997
- /** A one-line reason for the log — silence about a withdrawn service reads as a bug. */
106998
- function refusalReason(refusal) {
106999
- switch (refusal) {
107000
- case "no-enabled-stream": return "the camera has no enabled RTSP profile";
107001
- case "no-h264-stream": return "every enabled profile is H.265 — HKSV takes H.264 only, and a permanent transcode costs 128x a copy";
107002
- case "no-resolution": return "no enabled profile declares a resolution, so nothing honest could be advertised";
107003
- }
107004
- }
107005
- function height(entry) {
107006
- return entry.resolution?.height ?? 0;
107007
- }
107008
- function hasUsableResolution(entry) {
107009
- const r = entry.resolution;
107010
- return r !== void 0 && r.width > 0 && r.height > 0;
107011
- }
107012
- /** Publishers spell H.265 four ways; the same normalisation the broker uses. */
107013
- function normaliseCodec(codec) {
107014
- return (codec ?? "").toLowerCase().replace(/[.\s-]/g, "");
107015
- }
107016
- //#endregion
107017
107797
  //#region src/hksv/build-recording.ts
107018
107798
  /**
107019
107799
  * Assemble HomeKit Secure Video for one camera — the ADVERTISEMENT and the
@@ -107065,7 +107845,7 @@ async function buildHksvRecording(input) {
107065
107845
  note("profiles-unreadable", "cameraStreams.getProfileRtspEntries could not be read");
107066
107846
  return null;
107067
107847
  }
107068
- const choice = pickRecordingSource(entries);
107848
+ const choice = pickRecordingSource(entries, { allowTranscode: bctx.options.hapDeviceSettings?.allowTranscode !== false });
107069
107849
  if (!choice.ok) {
107070
107850
  log.info("export-hap: HKSV withheld — no recordable stream", { meta: {
107071
107851
  refusal: choice.refusal,
@@ -107075,9 +107855,10 @@ async function buildHksvRecording(input) {
107075
107855
  return null;
107076
107856
  }
107077
107857
  const source = choice.source;
107858
+ const decodeHwAccel = source.transcode ? await resolveDecodeHwAccel(bctx, log) : null;
107078
107859
  const gopMs = await readSourceGopMs(bctx, source.width, source.height);
107079
- const fragmentLengthMs = deriveFragmentLengthMs(gopMs);
107080
- if (fragmentLengthMs === null) {
107860
+ const fragmentLengthMs = source.transcode ? HKSV_FRAGMENT_LENGTHS_MS[0] : deriveFragmentLengthMs(gopMs);
107861
+ if (fragmentLengthMs === void 0 || fragmentLengthMs === null) {
107081
107862
  log.warn("export-hap: HKSV withheld — the camera key-frame interval is longer than any fragment length we advertise", { meta: {
107082
107863
  gopMs,
107083
107864
  brokerId: source.brokerId
@@ -107088,9 +107869,10 @@ async function buildHksvRecording(input) {
107088
107869
  const parsedProfile = CamProfileSchema.safeParse(source.profile);
107089
107870
  const recordedProfile = parsedProfile.success ? parsedProfile.data : null;
107090
107871
  const fps = resolveFps(input.fpsByProfile, source.profile);
107872
+ const out = recordingOutput(source);
107091
107873
  const options = buildRecordingOptions({
107092
- width: source.width,
107093
- height: source.height,
107874
+ width: out.width,
107875
+ height: out.height,
107094
107876
  fps,
107095
107877
  fragmentLengthMs
107096
107878
  });
@@ -107100,8 +107882,8 @@ async function buildHksvRecording(input) {
107100
107882
  logger: log,
107101
107883
  deviceId: numericDeviceId,
107102
107884
  store: clipStore,
107103
- width: source.width,
107104
- height: source.height,
107885
+ width: out.width,
107886
+ height: out.height,
107105
107887
  fragmentMs: fragmentLengthMs,
107106
107888
  ...recordedProfile === null ? {} : { profile: recordedProfile }
107107
107889
  });
@@ -107120,14 +107902,21 @@ async function buildHksvRecording(input) {
107120
107902
  spawnFn: node_child_process.spawn,
107121
107903
  source,
107122
107904
  fragmentMs,
107123
- audioActive
107905
+ audioActive,
107906
+ decodeHwAccel: decodeHwAccel?.hwaccel ?? null,
107907
+ ...decodeHwAccel === null || decodeHwAccel.extraInputArgs.length === 0 ? {} : { decodeInputArgs: decodeHwAccel.extraInputArgs }
107124
107908
  })
107125
107909
  });
107126
107910
  const advertisedResolution = options.video.resolutions[0];
107127
107911
  log.info("export-hap: HKSV ADVERTISED — recording is offered for this camera", { meta: {
107128
107912
  brokerId: source.brokerId,
107129
107913
  profile: source.profile,
107130
- resolution: `${source.width}x${source.height}`,
107914
+ resolution: `${out.width}x${out.height}`,
107915
+ branch: source.transcode ? "transcode" : "copy",
107916
+ ...source.transcode ? {
107917
+ sourceResolution: `${source.width}x${source.height}`,
107918
+ decodeHwAccel: decodeHwAccel?.hwaccel ?? "software"
107919
+ } : {},
107131
107920
  measuredFps: fps,
107132
107921
  advertisedFps: advertisedResolution?.[2] ?? null,
107133
107922
  fragmentLengthMs,
@@ -107142,6 +107931,27 @@ async function buildHksvRecording(input) {
107142
107931
  dispose: () => delegate.dispose()
107143
107932
  };
107144
107933
  }
107934
+ /**
107935
+ * The `-hwaccel` value for a transcoding prebuffer, and the flags that travel
107936
+ * with it.
107937
+ *
107938
+ * Software is a legitimate answer and is always NAMED: a camera that quietly
107939
+ * stopped using the GPU and one that never had it look identical in a CPU
107940
+ * graph, and on this branch the difference is most of a core, permanently.
107941
+ */
107942
+ async function resolveDecodeHwAccel(bctx, log) {
107943
+ const decision = selectHwDecode({
107944
+ transcode: true,
107945
+ reading: await probeDecoderHwaccel({
107946
+ source: decoderInfoSourceFromContext(bctx.ctx),
107947
+ log,
107948
+ memo: bctx.options.decodeMemos.reading
107949
+ }),
107950
+ platform: process.platform
107951
+ });
107952
+ if (decision.kind === "software") log.warn("export-hap: HKSV will transcode in SOFTWARE — this costs most of a core, permanently", { meta: { reason: decision.reason } });
107953
+ return decision;
107954
+ }
107145
107955
  /** `cameraStreams.getProfileRtspEntries`, or `null` when the cap is unreachable. */
107146
107956
  async function readProfileEntries(bctx) {
107147
107957
  try {
@@ -108137,13 +108947,12 @@ async function buildCameraAccessory(input) {
108137
108947
  bctx,
108138
108948
  streamingOptions: streams.streamingOptions
108139
108949
  }));
108140
- const recordingEnabled = options.hapDeviceSettings.hksvRecording === true;
108141
108950
  let recordingAudioActive = true;
108142
- const recording = recordingEnabled ? await buildHksvRecording({
108951
+ const recording = await buildHksvRecording({
108143
108952
  bctx,
108144
108953
  fpsByProfile: advertisedVideo.fpsByProfile,
108145
108954
  isAudioActive: () => recordingAudioActive
108146
- }) : null;
108955
+ });
108147
108956
  const controller = new (isDoorbell ? import_dist.DoorbellController : import_dist.CameraController)({
108148
108957
  delegate: streams.delegate,
108149
108958
  streamingOptions: streams.streamingOptions,
@@ -108392,38 +109201,7 @@ function syncStateToJson(map) {
108392
109201
  * that matters about it: it must resolve to NOT recording, or "off by default"
108393
109202
  * is true only for cameras exposed before the flip.
108394
109203
  */
108395
- var DEFAULT_DEVICE_SETTINGS = {
108396
- streamPreference: "auto",
108397
- hksvRecording: false
108398
- };
108399
- /**
108400
- * OFF unless explicitly switched on — operator decision 2026-09-21, reversing
108401
- * the 2026-08-08 flip:
108402
- *
108403
- * > *"il recording deve essere off di default, non deve essere attivo quando
108404
- * > si esporta una camera su HKSV"*
108405
- *
108406
- * What changed is not the cost the earlier record argued about — recording on
108407
- * the copy branch really is 0.7 % of a core and ~30 MB (D84), and cheap it
108408
- * remains. What changed is that on-by-default made the switch **say something
108409
- * the system was not doing**. Measured on 2026-09-21: 592, 587, 618 and 640
108410
- * all reported HomeKit recording ON while the advertisement was WITHHELD
108411
- * (`no-enabled-stream` — no enabled RTSP profile), so HomeKit never recorded
108412
- * them and the tee had nothing to keep. The operator was being told four
108413
- * cameras were recording because nobody had turned them off.
108414
- *
108415
- * Exposing a camera to HomeKit is a decision about EXPOSURE. Recording is a
108416
- * second decision and now has to be made.
108417
- *
108418
- * ABSENT resolves OFF — the load-bearing case, for the same reason it was
108419
- * before and with the opposite answer: a camera whose settings predate the
108420
- * field, or that was exposed before today, was never asked about. Every read
108421
- * goes through this one resolver (`=== true`), never a scattered `!== false`,
108422
- * because those two disagree about exactly that case.
108423
- */
108424
- function resolveHksvRecording(settings) {
108425
- return settings?.hksvRecording === true;
108426
- }
109204
+ var DEFAULT_DEVICE_SETTINGS = { streamPreference: "auto" };
108427
109205
  /**
108428
109206
  * Whether this camera offers Apple's NEW camera service alongside the classic
108429
109207
  * one — `camera-multi-tier-rtp-stream-management` (0x8031), where HEVC and 4K
@@ -108466,11 +109244,11 @@ var EXPORT_HAP_ADDON_ID = "export-hap";
108466
109244
  * stays and says the copies are off, which is D62's other half: an off switch
108467
109245
  * is REPORTED off, never made to look like a broken camera.
108468
109246
  */
108469
- function describeHksvCamera(entry, lastBuild) {
109247
+ function describeHksvCamera(entry, lastBuild, homekitRecording) {
108470
109248
  const isCamera = entry !== null && entry.mapperKind === "camera";
108471
109249
  return {
108472
109250
  exported: isCamera,
108473
- recording: isCamera && resolveHksvRecording(entry.settings),
109251
+ homekitRecording: isCamera ? homekitRecording : null,
108474
109252
  keepingClips: isCamera && resolveKeepHomekitClips(entry.settings),
108475
109253
  lastBuild
108476
109254
  };
@@ -108714,7 +109492,7 @@ var ExportHapAddon = class extends BaseAddon {
108714
109492
  const clipsProvider = createHksvVideoclipsProvider({
108715
109493
  logger: this.ctx.logger.child("hksv-clips"),
108716
109494
  addonId: EXPORT_HAP_ADDON_ID,
108717
- describeCamera: (deviceId) => describeHksvCamera(this.findEntry(deviceId), this.hksvBuilds.lastFor(deviceId)),
109495
+ describeCamera: (deviceId) => describeHksvCamera(this.findEntry(deviceId), this.hksvBuilds.lastFor(deviceId), readHomekitRecordingActive(this.exposed.get(String(deviceId))?.accessories ?? [])),
108718
109496
  readCatalog: async (deviceId) => await this.hksvClipStore?.listCatalog(deviceId) ?? { kind: "no-root" },
108719
109497
  locationRefusal: () => this.hksvClipLocation?.lastRefusal ?? null,
108720
109498
  mediaUrl: (deviceId, stem) => this.hksvClipUrl("media", deviceId, stem),
@@ -108870,7 +109648,6 @@ var ExportHapAddon = class extends BaseAddon {
108870
109648
  },
108871
109649
  hapDeviceSettings: {
108872
109650
  streamPreference: entrySettings.streamPreference ?? "auto",
108873
- hksvRecording: resolveHksvRecording(entrySettings),
108874
109651
  keepHomekitClips: resolveKeepHomekitClips(entrySettings),
108875
109652
  multiTierService: resolveMultiTierService(entrySettings),
108876
109653
  allowTranscode: entrySettings.allowTranscode !== false
@@ -108951,15 +109728,40 @@ var ExportHapAddon = class extends BaseAddon {
108951
109728
  features: device.features
108952
109729
  });
108953
109730
  const persisted = syncStateFromJson(this.config.exportSyncState).get(deviceId)?.fingerprint;
109731
+ const withToken = withRecordabilityToken({
109732
+ base: fresh,
109733
+ token: await this.readRecordabilityToken(deviceId, device.type),
109734
+ persisted
109735
+ });
108954
109736
  return resolveExportFingerprint({
108955
109737
  ready: device.probed ?? false,
108956
- fresh,
109738
+ fresh: withToken,
108957
109739
  persisted
108958
109740
  });
108959
109741
  } catch {
108960
109742
  return null;
108961
109743
  }
108962
109744
  }
109745
+ /**
109746
+ * The slot HKSV would record this camera from, for the fingerprint.
109747
+ *
109748
+ * `null` — do not move the fingerprint — for anything that is not a camera
109749
+ * (a switch has no recording decision, and stamping a constant on it would
109750
+ * republish every non-camera accessory once for nothing) and for a camera
109751
+ * whose profiles could not be read right now.
109752
+ */
109753
+ async readRecordabilityToken(deviceId, type) {
109754
+ if (pickMapperKind(type) !== "camera") return null;
109755
+ try {
109756
+ const entries = await (await this.ctx.fetchDevice(deviceId)).cameraStreams?.getProfileRtspEntries({});
109757
+ return recordabilityTokenFor({
109758
+ allowTranscode: this.findEntry(deviceId)?.settings?.allowTranscode !== false,
109759
+ entries: entries ?? null
109760
+ });
109761
+ } catch {
109762
+ return null;
109763
+ }
109764
+ }
108963
109765
  /** Persist a device's current export fingerprint into `exportSyncState`
108964
109766
  * (best-effort; called after a successful attach). */
108965
109767
  async persistFingerprint(deviceId, fingerprint) {
@@ -109225,7 +110027,6 @@ var ExportHapAddon = class extends BaseAddon {
109225
110027
  const enabled = entry !== null;
109226
110028
  const enabledKey = `hap:${deviceId}:enabled`;
109227
110029
  const streamPreferenceKey = `hap:${deviceId}:streamPreference`;
109228
- const hksvKey = `hap:${deviceId}:hksvRecording`;
109229
110030
  const multiTierKey = `hap:${deviceId}:multiTierService`;
109230
110031
  const useBridgeKey = `hap:${deviceId}:useBridge`;
109231
110032
  const allowTranscodeKey = `hap:${deviceId}:allowTranscode`;
@@ -109276,17 +110077,14 @@ var ExportHapAddon = class extends BaseAddon {
109276
110077
  immediate: true
109277
110078
  },
109278
110079
  {
109279
- type: "boolean",
109280
- key: hksvKey,
110080
+ type: "info",
110081
+ key: `hap:${deviceId}:hksvRecordingStatus`,
109281
110082
  label: "HomeKit recording (Secure Video)",
109282
- description: "Offer “Stream and Allow Recording” in iOS Home. Requires iCloud+ and a home hub. Keeps a continuous 8s prebuffer for this camera (~0.7% of one CPU core, H.264 sources only).",
109283
- style: "switch",
109284
- value: resolveHksvRecording(settings),
110083
+ content: describeHomekitRecording(readHomekitRecordingActive(this.exposed.get(String(deviceId))?.accessories ?? []), this.hksvBuilds.lastFor(deviceId)),
109285
110084
  showWhen: {
109286
110085
  field: enabledKey,
109287
110086
  equals: true
109288
- },
109289
- immediate: true
110087
+ }
109290
110088
  },
109291
110089
  {
109292
110090
  type: "boolean",
@@ -109309,7 +110107,7 @@ var ExportHapAddon = class extends BaseAddon {
109309
110107
  style: "switch",
109310
110108
  value: resolveKeepHomekitClips(settings),
109311
110109
  showWhen: {
109312
- field: hksvKey,
110110
+ field: enabledKey,
109313
110111
  equals: true
109314
110112
  },
109315
110113
  immediate: true
@@ -109409,7 +110207,6 @@ var ExportHapAddon = class extends BaseAddon {
109409
110207
  const wasEnabled = this.exposed.has(deviceIdStr);
109410
110208
  const enabledKey = `hap:${deviceId}:enabled`;
109411
110209
  const streamPreferenceKey = `hap:${deviceId}:streamPreference`;
109412
- const hksvKey = `hap:${deviceId}:hksvRecording`;
109413
110210
  const useBridgeKey = `hap:${deviceId}:useBridge`;
109414
110211
  const allowTranscodeKey = `hap:${deviceId}:allowTranscode`;
109415
110212
  const multiTierKey = `hap:${deviceId}:multiTierService`;
@@ -109418,7 +110215,6 @@ var ExportHapAddon = class extends BaseAddon {
109418
110215
  const enabledValue = enabledKey in patch ? Boolean(patch[enabledKey]) : wasEnabled;
109419
110216
  const streamPreferenceRaw = streamPreferenceKey in patch ? patch[streamPreferenceKey] : current?.settings?.streamPreference;
109420
110217
  const streamPreference = typeof streamPreferenceRaw === "string" && streamPreferenceRaw.trim().length > 0 ? streamPreferenceRaw : "auto";
109421
- const hksvRecording = hksvKey in patch ? Boolean(patch[hksvKey]) : resolveHksvRecording(current?.settings);
109422
110218
  const useBridge = useBridgeKey in patch ? Boolean(patch[useBridgeKey]) : current?.settings?.useBridge !== false;
109423
110219
  const allowTranscode = allowTranscodeKey in patch ? Boolean(patch[allowTranscodeKey]) : current?.settings?.allowTranscode !== false;
109424
110220
  const multiTierService = multiTierKey in patch ? Boolean(patch[multiTierKey]) : resolveMultiTierService(current?.settings);
@@ -109430,7 +110226,6 @@ var ExportHapAddon = class extends BaseAddon {
109430
110226
  const nextSettings = {
109431
110227
  ...current?.settings ?? DEFAULT_DEVICE_SETTINGS,
109432
110228
  streamPreference,
109433
- hksvRecording,
109434
110229
  useBridge,
109435
110230
  allowTranscode,
109436
110231
  multiTierService,
@@ -109447,7 +110242,6 @@ var ExportHapAddon = class extends BaseAddon {
109447
110242
  return { success: true };
109448
110243
  }
109449
110244
  const currentPref = current?.settings?.streamPreference ?? "auto";
109450
- const currentHksv = resolveHksvRecording(current?.settings);
109451
110245
  const currentKeepClips = resolveKeepHomekitClips(current?.settings);
109452
110246
  const mapperKind = current?.mapperKind ?? "generic";
109453
110247
  const wasBridged = shouldBridge({
@@ -109459,16 +110253,12 @@ var ExportHapAddon = class extends BaseAddon {
109459
110253
  useBridge
109460
110254
  });
109461
110255
  await this.updateEntrySettings(deviceIdStr, nextSettings);
109462
- if (currentPref !== streamPreference || currentHksv !== hksvRecording || currentKeepClips !== keepClips || wasBridged !== willBridge) {
110256
+ if (currentPref !== streamPreference || currentKeepClips !== keepClips || wasBridged !== willBridge) {
109463
110257
  log.info("export-hap: per-camera export settings changed — refreshing accessory", { meta: {
109464
110258
  streamPreference: {
109465
110259
  from: currentPref,
109466
110260
  to: streamPreference
109467
110261
  },
109468
- hksvRecording: {
109469
- from: currentHksv,
109470
- to: hksvRecording
109471
- },
109472
110262
  keepClips: {
109473
110263
  from: currentKeepClips,
109474
110264
  to: keepClips
@@ -109565,7 +110355,6 @@ exports.describeHksvCamera = describeHksvCamera;
109565
110355
  exports.initHapStorage = initHapStorage;
109566
110356
  exports.publishStandalone = publishStandalone;
109567
110357
  exports.resolveClipRetentionDays = resolveClipRetentionDays;
109568
- exports.resolveHksvRecording = resolveHksvRecording;
109569
110358
  exports.resolveKeepHomekitClips = resolveKeepHomekitClips;
109570
110359
  exports.resolveMultiTierService = resolveMultiTierService;
109571
110360
  exports.unpublishAccessory = unpublishAccessory;