@camstack/addon-provider-homeassistant 1.2.642 → 1.2.644

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.
@@ -1,4 +1,4 @@
1
- //#region ../types/dist/event-category-DmZDPDf_.mjs
1
+ //#region ../types/dist/event-category-CaURIwQA.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -714,6 +714,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
714
714
  EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
715
715
  /** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
716
716
  EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
717
+ /** A migration failed or was blocked; the Alert Center re-reads the ledger. */
718
+ EventCategory["MigrationFailed"] = "migration.failed";
719
+ /** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
720
+ EventCategory["MigrationApplied"] = "migration.applied";
717
721
  EventCategory["DeviceUpdated"] = "device.updated";
718
722
  /**
719
723
  * Transport-level connectivity. Emitted by the device driver when
@@ -776,6 +780,378 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
776
780
  return EventCategory;
777
781
  }({});
778
782
  //#endregion
783
+ //#region ../types/dist/device-type-CEqjmUnR.mjs
784
+ /**
785
+ import { errMsg } from '@camstack/types'
786
+ * Extract a human-readable message from an unknown error value.
787
+ * Replaces the ubiquitous `errMsg(err)` pattern.
788
+ */
789
+ function errMsg(err) {
790
+ if (err instanceof Error) return err.message;
791
+ if (typeof err === "string") return err;
792
+ return String(err);
793
+ }
794
+ var DeviceType = /* @__PURE__ */ function(DeviceType) {
795
+ DeviceType["Camera"] = "camera";
796
+ DeviceType["Hub"] = "hub";
797
+ DeviceType["Light"] = "light";
798
+ DeviceType["Siren"] = "siren";
799
+ DeviceType["Switch"] = "switch";
800
+ DeviceType["Sensor"] = "sensor";
801
+ DeviceType["Thermostat"] = "thermostat";
802
+ /** Air-conditioner / heat-pump climate device (HVAC) — shares the
803
+ * `climate-control` cap surface with `Thermostat` but renders a
804
+ * dedicated AC-appropriate control UI (mode chips, fan speed,
805
+ * independent vertical/horizontal swing). Sources: native Gree, and
806
+ * reusable by other AC integrations. */
807
+ DeviceType["Climate"] = "climate";
808
+ DeviceType["Button"] = "button";
809
+ /** Generic stateless event emitter — carries a device's EXACT declared
810
+ * event vocabulary verbatim (no normalization). Installed with the
811
+ * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
812
+ * HA bus events (e.g. `zha_event`, generic). */
813
+ DeviceType["EventEmitter"] = "event-emitter";
814
+ /** Firmware/software update entity — current vs available version,
815
+ * updatable flag, update state, and an install action. Installed with
816
+ * the `update` cap. Sources: Homematic firmware-update channels (and
817
+ * reusable by other providers, e.g. HA `update.*` entities). */
818
+ DeviceType["Update"] = "update";
819
+ DeviceType["Generic"] = "generic";
820
+ /** Generic notification delivery target (HA `notify.<service>`, future
821
+ * Telegram / Discord / ntfy / SMTP, …). One device per delivery
822
+ * endpoint; the `notifier` cap defines the send surface. */
823
+ DeviceType["Notifier"] = "notifier";
824
+ /** Pre-recorded action sequence with optional parameters
825
+ * (HA `script.*`). Runnable via `script-runner` cap. */
826
+ DeviceType["Script"] = "script";
827
+ /** Automation rule (HA `automation.*`) — enable/disable + manual
828
+ * trigger surface exposed via `automation-control` cap. */
829
+ DeviceType["Automation"] = "automation";
830
+ /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
831
+ DeviceType["Lock"] = "lock";
832
+ /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
833
+ * `valve.*`). `cover` cap with sub-roles for variant. */
834
+ DeviceType["Cover"] = "cover";
835
+ /** Pipe / water / gas valve with open/close/stop and optional
836
+ * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
837
+ * modelled on the same open/closed lifecycle. */
838
+ DeviceType["Valve"] = "valve";
839
+ /** Humidifier / dehumidifier with on/off + target humidity + mode
840
+ * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
841
+ * modelled on the same target / mode lifecycle. */
842
+ DeviceType["Humidifier"] = "humidifier";
843
+ /** Water heater / boiler with target temperature + operation mode +
844
+ * away mode (HA `water_heater.*`). `water-heater` cap — a
845
+ * climate-family actuator. */
846
+ DeviceType["WaterHeater"] = "water-heater";
847
+ /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
848
+ DeviceType["Fan"] = "fan";
849
+ /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
850
+ * the camera surface — those use `Camera`. `media-player` cap. */
851
+ DeviceType["MediaPlayer"] = "media-player";
852
+ /** Security panel / alarm system (HA `alarm_control_panel.*`).
853
+ * `alarm-panel` cap. */
854
+ DeviceType["AlarmPanel"] = "alarm-panel";
855
+ /** Generic user-settable input (HA `number` / `input_number` / `select`
856
+ * / `input_select` / `text` / `input_text` / `input_datetime`).
857
+ * Sub-type via `DeviceRole`: NumericControl / SelectControl /
858
+ * TextControl / DateTimeControl. */
859
+ DeviceType["Control"] = "control";
860
+ /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
861
+ * `presence` cap. */
862
+ DeviceType["Presence"] = "presence";
863
+ /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
864
+ * `weather` cap. */
865
+ DeviceType["Weather"] = "weather";
866
+ /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
867
+ DeviceType["Vacuum"] = "vacuum";
868
+ /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
869
+ * `lawn-mower-control` cap. */
870
+ DeviceType["LawnMower"] = "lawn-mower";
871
+ /** Physical HA device group — parent container for entity-children
872
+ * adopted from a single HA device entry. Not renderable as a
873
+ * standalone device; exists only to anchor child entities. */
874
+ DeviceType["Container"] = "container";
875
+ /** Single still-image entity (HA `image.*`). Read-only display of an
876
+ * `entity_picture` signed URL the browser loads directly. `image` cap. */
877
+ DeviceType["Image"] = "image";
878
+ /** Smart pet feeder — cloud-connected food dispenser with a bowl food
879
+ * level, battery, desiccant life, feeding state and manual-feed /
880
+ * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
881
+ * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
882
+ * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
883
+ * integrations sharing the same food/desiccant/hopper surface. */
884
+ DeviceType["PetFeeder"] = "pet-feeder";
885
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
886
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
887
+ * of a container and is BUILT as a composed device (D967). */
888
+ DeviceType["PetFountain"] = "pet-fountain";
889
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
890
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
891
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
892
+ * A plug-built dishwasher carries running-state and power instead. */
893
+ DeviceType["Dishwasher"] = "dishwasher";
894
+ /** Washing machine. A plug-built one is running while its power stays
895
+ * above a threshold. No program cap — that surface is the dishwasher's. */
896
+ DeviceType["Washer"] = "washer";
897
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
898
+ DeviceType["Dryer"] = "dryer";
899
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
900
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
901
+ * address, connection state) and the actions the box really supports
902
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
903
+ * adopted beside it — and its connection settings stay on the integration. */
904
+ DeviceType["Gateway"] = "gateway";
905
+ return DeviceType;
906
+ }({});
907
+ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
908
+ DeviceFeature["BatteryOperated"] = "battery-operated";
909
+ DeviceFeature["Rebootable"] = "rebootable";
910
+ /**
911
+ * Device supports an on-demand re-sync of its derived spec with its
912
+ * upstream source — drives the generic Re-sync button. The owning
913
+ * provider implements the action via the `device-adoption.resync` cap.
914
+ */
915
+ DeviceFeature["Resyncable"] = "resyncable";
916
+ DeviceFeature["NativeSnapshot"] = "native-snapshot";
917
+ DeviceFeature["DoorbellButton"] = "doorbell-button";
918
+ DeviceFeature["TwoWayAudio"] = "two-way-audio";
919
+ DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
920
+ /**
921
+ * Camera supports the on-firmware autotrack subsystem (subject-
922
+ * following). Distinct from `PanTiltZoom` because not every PTZ
923
+ * camera ships autotrack — the admin UI uses this flag to gate
924
+ * the autotrack toggle / settings card without re-deriving from
925
+ * the cap registry. Mirrors `ptz-autotrack` cap registration:
926
+ * driver sets this feature when probe confirms the firmware
927
+ * surface, and registers the cap in the same code path.
928
+ */
929
+ DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
930
+ /**
931
+ * Accessory exposes a "trigger on motion" toggle — the parent camera's
932
+ * motion detection automatically activates this device. Mirrors
933
+ * `motion-trigger` cap registration: drivers set this feature in the
934
+ * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
935
+ *
936
+ * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
937
+ * fast scalar without binding fetch), notifier rules, and `listAll`
938
+ * filters that want "all devices with on-motion behaviour".
939
+ */
940
+ DeviceFeature["MotionTrigger"] = "motion-trigger";
941
+ /** Light supports rgb-triplet color via `color` cap. */
942
+ DeviceFeature["LightColorRgb"] = "light-color-rgb";
943
+ /** Light supports HSV color via `color` cap. */
944
+ DeviceFeature["LightColorHsv"] = "light-color-hsv";
945
+ /** Light supports color-temperature (mired) via `color` cap. */
946
+ DeviceFeature["LightColorMired"] = "light-color-mired";
947
+ /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
948
+ * targetHigh). Gates the range slider UI. */
949
+ DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
950
+ /** Thermostat exposes target humidity and/or current humidity
951
+ * readings. Gates the humidity controls. */
952
+ DeviceFeature["ClimateHumidity"] = "climate-humidity";
953
+ /** Thermostat exposes a fan-mode selector. */
954
+ DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
955
+ /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
956
+ DeviceFeature["ClimatePreset"] = "climate-preset";
957
+ /** Thermostat exposes a vertical louver swing toggle. Gates the
958
+ * vertical-swing switch in the climate UI. Independent of horizontal. */
959
+ DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
960
+ /** Thermostat exposes a horizontal louver swing toggle. Gates the
961
+ * horizontal-swing switch in the climate UI. Independent of vertical. */
962
+ DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
963
+ /** Cover exposes intermediate position control (0..100). Gates the
964
+ * position slider UI. */
965
+ DeviceFeature["CoverPositionable"] = "cover-positionable";
966
+ /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
967
+ DeviceFeature["CoverTilt"] = "cover-tilt";
968
+ /** Valve exposes intermediate position control (0..100). Gates the
969
+ * position slider / drag surface UI. */
970
+ DeviceFeature["ValvePositionable"] = "valve-positionable";
971
+ /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
972
+ DeviceFeature["FanSpeed"] = "fan-speed";
973
+ /** Fan exposes a preset mode selector. */
974
+ DeviceFeature["FanPreset"] = "fan-preset";
975
+ /** Fan exposes blade direction (forward/reverse) — typical of
976
+ * ceiling fans. */
977
+ DeviceFeature["FanDirection"] = "fan-direction";
978
+ /** Fan exposes an oscillation toggle. */
979
+ DeviceFeature["FanOscillating"] = "fan-oscillating";
980
+ /** Lock requires a PIN code on lock/unlock. Gates the code-entry
981
+ * field on the UI lock-controls panel. */
982
+ DeviceFeature["LockPinRequired"] = "lock-pin-required";
983
+ /** Lock supports a latch-release ("open door") action distinct from
984
+ * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
985
+ * `supported_features`. Gates the Open Door button in the UI. */
986
+ DeviceFeature["LockOpen"] = "lock-open";
987
+ /** Media player exposes a seek-to-position surface. */
988
+ DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
989
+ /** Media player exposes a volume-level setter. */
990
+ DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
991
+ /** Media player exposes a mute toggle distinct from volume=0. */
992
+ DeviceFeature["MediaPlayerMute"] = "media-player-mute";
993
+ /** Media player exposes a shuffle toggle. */
994
+ DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
995
+ /** Media player exposes a repeat mode (off / all / one). */
996
+ DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
997
+ /** Media player exposes a source / input selector. */
998
+ DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
999
+ /** Media player exposes a play-arbitrary-media surface (URL / id). */
1000
+ DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
1001
+ /** Media player exposes next-track. */
1002
+ DeviceFeature["MediaPlayerNext"] = "media-player-next";
1003
+ /** Media player exposes previous-track. */
1004
+ DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1005
+ /** Media player exposes stop distinct from pause. */
1006
+ DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1007
+ /** Alarm panel requires a PIN code on arm/disarm. */
1008
+ DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1009
+ /** Presence device carries GPS coordinates (lat/lng/accuracy) in
1010
+ * addition to a textual location. */
1011
+ DeviceFeature["PresenceGps"] = "presence-gps";
1012
+ /** Notifier accepts an inline / URL image attachment. */
1013
+ DeviceFeature["NotifierImage"] = "notifier-image";
1014
+ /** Notifier accepts a priority hint (high/normal/low). */
1015
+ DeviceFeature["NotifierPriority"] = "notifier-priority";
1016
+ /** Notifier accepts a free-form `data` payload for platform-specific
1017
+ * fields. */
1018
+ DeviceFeature["NotifierData"] = "notifier-data";
1019
+ /** Notifier supports interactive action buttons / callbacks. */
1020
+ DeviceFeature["NotifierActions"] = "notifier-actions";
1021
+ /** Notifier supports per-call recipient targeting (multi-user). */
1022
+ DeviceFeature["NotifierRecipients"] = "notifier-recipients";
1023
+ /** Script runner accepts a variables map on each run invocation. */
1024
+ DeviceFeature["ScriptVariables"] = "script-variables";
1025
+ /** Automation `trigger` accepts a skipCondition flag — fires the
1026
+ * automation's actions while bypassing its condition block. */
1027
+ DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
1028
+ /** Robot vacuum exposes a live cleaning map (image child). Gates the
1029
+ * map tile in the vacuum UI. */
1030
+ DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
1031
+ /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
1032
+ * switches group. */
1033
+ DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
1034
+ /** Robot mower exposes a live mowing map (SVG image child). Gates the
1035
+ * mower map tile. */
1036
+ DeviceFeature["MowerHasMap"] = "mower-has-map";
1037
+ /** Robot mower exposes targeted mowing (all-area / zones / edges /
1038
+ * spots) — gates the Mow action + map selectors. */
1039
+ DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
1040
+ return DeviceFeature;
1041
+ }({});
1042
+ /**
1043
+ * Semantic role a device plays within its parent. Populated by driver
1044
+ * addons when creating accessory devices (Reolink siren/floodlight/
1045
+ * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
1046
+ * admin UI to pick icons, labels, and widgets — a `Switch` with
1047
+ * `role: Floodlight` renders as a bulb with a brightness slider,
1048
+ * whereas a `Switch` with `role: Siren` renders as a klaxon.
1049
+ *
1050
+ * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
1051
+ * sqlite as a nullable TEXT column — old rows keep working unchanged.
1052
+ */
1053
+ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
1054
+ DeviceRole["Siren"] = "siren";
1055
+ DeviceRole["Floodlight"] = "floodlight";
1056
+ DeviceRole["Spotlight"] = "spotlight";
1057
+ DeviceRole["PirSensor"] = "pir-sensor";
1058
+ DeviceRole["Chime"] = "chime";
1059
+ DeviceRole["Autotrack"] = "autotrack";
1060
+ DeviceRole["Nightvision"] = "nightvision";
1061
+ DeviceRole["PrivacyMask"] = "privacy-mask";
1062
+ DeviceRole["Doorbell"] = "doorbell";
1063
+ /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
1064
+ DeviceRole["CoverBlind"] = "cover-blind";
1065
+ /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
1066
+ DeviceRole["CoverShutter"] = "cover-shutter";
1067
+ /** Exterior awning. HA `device_class: awning`. */
1068
+ DeviceRole["CoverAwning"] = "cover-awning";
1069
+ /** Curtain / drape. HA `device_class: curtain`. */
1070
+ DeviceRole["CoverCurtain"] = "cover-curtain";
1071
+ /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
1072
+ DeviceRole["CoverGarage"] = "cover-garage";
1073
+ /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
1074
+ DeviceRole["CoverGate"] = "cover-gate";
1075
+ /** Door. HA `device_class: door`. A security actuator: exports gate it. */
1076
+ DeviceRole["CoverDoor"] = "cover-door";
1077
+ /** Window. HA `device_class: window`. */
1078
+ DeviceRole["CoverWindow"] = "cover-window";
1079
+ /** Virtual HA toggle (input_boolean.*) — distinguishable from a
1080
+ * real Switch device for UI rendering / export adapters. */
1081
+ DeviceRole["BinaryHelper"] = "binary-helper";
1082
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
1083
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
1084
+ DeviceRole["Outlet"] = "outlet";
1085
+ /** Generic motion / occupancy / moving event source. Distinct from
1086
+ * the camera accessory PirSensor role: that one is a camera child;
1087
+ * this is a standalone HA / 3rd-party motion sensor. */
1088
+ DeviceRole["MotionSensor"] = "motion-sensor";
1089
+ DeviceRole["ContactSensor"] = "contact-sensor";
1090
+ DeviceRole["LeakSensor"] = "leak-sensor";
1091
+ DeviceRole["SmokeSensor"] = "smoke-sensor";
1092
+ DeviceRole["COSensor"] = "co-sensor";
1093
+ DeviceRole["GasSensor"] = "gas-sensor";
1094
+ DeviceRole["TamperSensor"] = "tamper-sensor";
1095
+ DeviceRole["VibrationSensor"] = "vibration-sensor";
1096
+ DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
1097
+ DeviceRole["SoundSensor"] = "sound-sensor";
1098
+ /** Fallback for `binary_sensor` without a known `device_class`. */
1099
+ DeviceRole["BinarySensor"] = "binary-sensor";
1100
+ DeviceRole["TemperatureSensor"] = "temperature-sensor";
1101
+ DeviceRole["HumiditySensor"] = "humidity-sensor";
1102
+ DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
1103
+ DeviceRole["PressureSensor"] = "pressure-sensor";
1104
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
1105
+ DeviceRole["WindSensor"] = "wind-sensor";
1106
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
1107
+ DeviceRole["RainSensor"] = "rain-sensor";
1108
+ /** UV index (weather-station `uv-sensor` cap). */
1109
+ DeviceRole["UvSensor"] = "uv-sensor";
1110
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
1111
+ * Distinct from AmbientLightSensor (lux). */
1112
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
1113
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
1114
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
1115
+ DeviceRole["PowerSensor"] = "power-sensor";
1116
+ DeviceRole["EnergySensor"] = "energy-sensor";
1117
+ DeviceRole["VoltageSensor"] = "voltage-sensor";
1118
+ DeviceRole["CurrentSensor"] = "current-sensor";
1119
+ DeviceRole["AirQualitySensor"] = "air-quality-sensor";
1120
+ /** Battery level (numeric % via `sensor` OR low-bool via
1121
+ * `binary_sensor` — the cap distinguishes via the value type). */
1122
+ DeviceRole["BatterySensor"] = "battery-sensor";
1123
+ /** Fallback for `sensor` numeric without a known `device_class`. */
1124
+ DeviceRole["NumericSensor"] = "numeric-sensor";
1125
+ /** String / enum state (HA `sensor` with `state_class: enum` or
1126
+ * `attributes.options`). */
1127
+ DeviceRole["EnumSensor"] = "enum-sensor";
1128
+ /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
1129
+ * or `date`). The slice carries the raw ISO string verbatim (hosted on
1130
+ * the `enum-sensor` cap); the UI renders it locale-formatted. */
1131
+ DeviceRole["DateTimeSensor"] = "datetime-sensor";
1132
+ /** Last-resort fallback when nothing else matches. */
1133
+ DeviceRole["GenericSensor"] = "generic-sensor";
1134
+ DeviceRole["NumericControl"] = "numeric-control";
1135
+ DeviceRole["SelectControl"] = "select-control";
1136
+ DeviceRole["TextControl"] = "text-control";
1137
+ DeviceRole["DateTimeControl"] = "datetime-control";
1138
+ /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
1139
+ * rich features (image, priority, channel routing). */
1140
+ DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
1141
+ /** Chat / messaging service (HA `notify.telegram_*`,
1142
+ * `notify.discord_*`, etc.). */
1143
+ DeviceRole["MessagingNotifier"] = "messaging-notifier";
1144
+ /** Email-based delivery (HA `notify.smtp`, etc.). */
1145
+ DeviceRole["EmailNotifier"] = "email-notifier";
1146
+ /** Fallback when the notifier service name doesn't match a known
1147
+ * pattern. */
1148
+ DeviceRole["GenericNotifier"] = "generic-notifier";
1149
+ /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
1150
+ * cap. A `DeviceType.Generic` device. */
1151
+ DeviceRole["IrTransmitter"] = "ir-transmitter";
1152
+ return DeviceRole;
1153
+ }({});
1154
+ //#endregion
779
1155
  //#region ../../node_modules/zod/v4/core/core.js
780
1156
  var _a$1;
781
1157
  function $constructor(name, initializer, params) {
@@ -5587,7 +5963,7 @@ var ZodIssueCode = {
5587
5963
  var ZodFirstPartyTypeKind;
5588
5964
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5589
5965
  //#endregion
5590
- //#region ../types/dist/sleep-SX9bkW3_.mjs
5966
+ //#region ../types/dist/sleep-CSYuxssh.mjs
5591
5967
  /**
5592
5968
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5593
5969
  * window to float samples (D455).
@@ -6103,6 +6479,102 @@ function emitReadiness(bus, params) {
6103
6479
  ts
6104
6480
  }));
6105
6481
  }
6482
+ var NOT_RUN = {
6483
+ declared: false,
6484
+ snapshot: null,
6485
+ applied: [],
6486
+ failed: [],
6487
+ blocked: [],
6488
+ skipped: [],
6489
+ blockOwner: []
6490
+ };
6491
+ /**
6492
+ * Offer `migrations` to the engine and run what it says to run, in its order. Never
6493
+ * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6494
+ * because the hub (and this addon's boot) must not hang on a migration.
6495
+ */
6496
+ async function runAddonMigrations(host, migrations, logger) {
6497
+ const first = migrations[0];
6498
+ if (first === void 0) return NOT_RUN;
6499
+ const owner = first.meta.owner;
6500
+ const log = logger.withTags({ migrationOwner: owner });
6501
+ const byId = new Map(migrations.map((m) => [m.meta.id, m]));
6502
+ let declaration;
6503
+ try {
6504
+ declaration = await host.declareOwner({
6505
+ owner,
6506
+ metas: migrations.map((m) => m.meta)
6507
+ });
6508
+ } catch (err) {
6509
+ log.error("the migration engine refused or could not take this addon catalogue — none ran", { meta: { error: errMsg(err) } });
6510
+ return {
6511
+ ...NOT_RUN,
6512
+ blockOwner: migrations.filter((m) => m.meta.onFailure === "block-owner").map((m) => m.meta.id)
6513
+ };
6514
+ }
6515
+ const applied = [];
6516
+ const failed = [];
6517
+ const blocked = [];
6518
+ const skipped = [];
6519
+ const settle = (id, status) => {
6520
+ if (status === "applied") applied.push(id);
6521
+ else if (status === "failed") failed.push(id);
6522
+ else if (status === "blocked") blocked.push(id);
6523
+ else skipped.push(id);
6524
+ };
6525
+ for (const step of declaration.steps) {
6526
+ const bound = byId.get(step.id);
6527
+ if (bound === void 0 || step.action === "skip") {
6528
+ skipped.push(step.id);
6529
+ continue;
6530
+ }
6531
+ const tags = { migrationId: step.id };
6532
+ try {
6533
+ const gate = await host.startMigration({ id: step.id });
6534
+ if (!gate.proceed) {
6535
+ log.warn("migration not run", {
6536
+ tags,
6537
+ meta: {
6538
+ state: gate.state,
6539
+ reason: gate.reason
6540
+ }
6541
+ });
6542
+ settle(step.id, gate.state === "pending" ? "blocked" : gate.state);
6543
+ continue;
6544
+ }
6545
+ const result = await bound.execute();
6546
+ const outcome = await host.finishMigration({
6547
+ id: step.id,
6548
+ result
6549
+ });
6550
+ if (outcome.status === "applied") log.info("migration applied", {
6551
+ tags,
6552
+ meta: { rowsChanged: outcome.rowsChanged }
6553
+ });
6554
+ else log.error("migration failed", {
6555
+ tags,
6556
+ meta: { error: outcome.error }
6557
+ });
6558
+ settle(step.id, outcome.status);
6559
+ } catch (err) {
6560
+ log.error("migration could not be completed with the engine", {
6561
+ tags,
6562
+ meta: { error: errMsg(err) }
6563
+ });
6564
+ failed.push(step.id);
6565
+ }
6566
+ }
6567
+ const blockOwner = [...failed, ...blocked].filter((id) => byId.get(id)?.meta.onFailure === "block-owner");
6568
+ return {
6569
+ declared: true,
6570
+ snapshot: declaration.snapshot,
6571
+ applied,
6572
+ failed,
6573
+ blocked,
6574
+ skipped,
6575
+ blockOwner
6576
+ };
6577
+ }
6106
6578
  /**
6107
6579
  * Build a {@link DurableState} over a single store key. Transport-agnostic:
6108
6580
  * `read`/`write` are the addon-store or device-store accessors. The whole
@@ -7158,6 +7630,28 @@ var BaseAddon = class {
7158
7630
  return normalized;
7159
7631
  }
7160
7632
  /**
7633
+ * Run this addon's migration catalogue (D987 M3) through the hub's `migrations`
7634
+ * engine. Call it at the point of the boot where the data must already have the new
7635
+ * shape — `onInitialize`, or `onRestoreDevices` for a migration that needs the
7636
+ * restored devices — with the catalogue bound to the addon's ports
7637
+ * (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
7638
+ *
7639
+ * An engine that cannot be reached or refuses the catalogue is logged and runs
7640
+ * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7641
+ * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7642
+ * serve data whose shape it cannot trust; `tolerate` migrations never do.
7643
+ */
7644
+ async runMigrations(migrations) {
7645
+ const api = this.ctx.api.migrations;
7646
+ const report = await runAddonMigrations({
7647
+ declareOwner: (input) => api.declareOwner.mutate(input),
7648
+ startMigration: (input) => api.startMigration.mutate(input),
7649
+ finishMigration: (input) => api.finishMigration.mutate(input)
7650
+ }, migrations, this.ctx.logger);
7651
+ if (report.blockOwner.length > 0) throw new Error(`migration(s) ${report.blockOwner.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
7652
+ return report;
7653
+ }
7654
+ /**
7161
7655
  * Called by the isolated-process runner AFTER `broker.start()`.
7162
7656
  * In-process addons never need this because the hub broker is already
7163
7657
  * running when `initialize()` fires. For forked children the broker
@@ -7290,6 +7784,26 @@ var BaseAddon = class {
7290
7784
  * alternative — skip it — turns a rename into a surface that quietly
7291
7785
  * empties, which looks exactly like an addon with nothing to configure.
7292
7786
  */
7787
+ /**
7788
+ * The form schema of ONE integration instance, or `null` to use {@link globalSettingsSchema}.
7789
+ *
7790
+ * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7791
+ * needs its connection fields, the default integration the addon's own sections. It is the
7792
+ * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7793
+ * unchanged (`integrations.setSettings` for an account integration).
7794
+ */
7795
+ async integrationSettingsSchema(_instance) {
7796
+ return null;
7797
+ }
7798
+ /**
7799
+ * What the create wizard and the integration edit page render for one instance: the addon's
7800
+ * per-instance schema when it declares one, else its global schema (what they always showed).
7801
+ */
7802
+ async getIntegrationFormSchema(instance, nodeId) {
7803
+ const scoped = await this.integrationSettingsSchema(instance);
7804
+ if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7805
+ return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7806
+ }
7293
7807
  async getIntegrationSettings(nodeId) {
7294
7808
  const declared = this.integrationSettingSections();
7295
7809
  if (declared.length === 0) return null;
@@ -8435,410 +8949,6 @@ function defineEntryKind(name, spec) {
8435
8949
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8436
8950
  return defined;
8437
8951
  }
8438
- var READING_QUANTITIES = [
8439
- "temperature",
8440
- "pressure",
8441
- "speed",
8442
- "precipitation",
8443
- "precipitation-rate",
8444
- "length",
8445
- "area",
8446
- "illuminance",
8447
- "irradiance",
8448
- "power",
8449
- "energy",
8450
- "voltage",
8451
- "current",
8452
- "apparent-power",
8453
- "humidity",
8454
- "percentage",
8455
- "concentration-volume",
8456
- "concentration-mass",
8457
- "concentration-ppb",
8458
- "volume",
8459
- "duration",
8460
- "duration-ms",
8461
- "information",
8462
- "sound-level",
8463
- "signal-level",
8464
- "colour-temperature",
8465
- "angle",
8466
- "bitrate",
8467
- "rate",
8468
- "mass",
8469
- "index",
8470
- "ratio",
8471
- "count",
8472
- "none",
8473
- "device"
8474
- ];
8475
- var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8476
- var QUANTITY_SET = new Set(READING_QUANTITIES);
8477
- function isReadingQuantity(value) {
8478
- return typeof value === "string" && QUANTITY_SET.has(value);
8479
- }
8480
- /**
8481
- * Stored unit symbol for a quantity. `none` means the number is dimensionless
8482
- * (or an index / ratio / count). `device` means the cap has no single unit.
8483
- */
8484
- var QUANTITY_BASE = {
8485
- temperature: "°C",
8486
- pressure: "hPa",
8487
- speed: "m/s",
8488
- precipitation: "mm",
8489
- "precipitation-rate": "mm/h",
8490
- length: "m",
8491
- area: "m²",
8492
- illuminance: "lx",
8493
- irradiance: "W/m²",
8494
- power: "W",
8495
- energy: "kWh",
8496
- voltage: "V",
8497
- current: "A",
8498
- "apparent-power": "VA",
8499
- humidity: "%",
8500
- percentage: "%",
8501
- "concentration-volume": "ppm",
8502
- "concentration-mass": "µg/m³",
8503
- "concentration-ppb": "ppb",
8504
- volume: "L",
8505
- duration: "s",
8506
- "duration-ms": "ms",
8507
- information: "B",
8508
- "sound-level": "dBFS",
8509
- "signal-level": "dBm",
8510
- "colour-temperature": "mired",
8511
- angle: "°",
8512
- bitrate: "kbps",
8513
- rate: "fps",
8514
- mass: "kg",
8515
- index: "none",
8516
- ratio: "none",
8517
- count: "none",
8518
- none: "none",
8519
- device: "device"
8520
- };
8521
- /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8522
- function baseUnitOf(quantity) {
8523
- const symbol = QUANTITY_BASE[quantity];
8524
- if (symbol === "none" || symbol === "device") return void 0;
8525
- return symbol;
8526
- }
8527
- function readingMeta(quantity, extras) {
8528
- return {
8529
- quantity,
8530
- ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8531
- ...extras?.label !== void 0 ? { label: extras.label } : {}
8532
- };
8533
- }
8534
- /** Shared Zod schemas used across streaming capabilities. */
8535
- var CamProfileSchema = _enum([
8536
- "high",
8537
- "mid",
8538
- "low"
8539
- ]);
8540
- /**
8541
- * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
8542
- *
8543
- * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
8544
- * that consumer owns". It lives beside `CamProfile` because a rung is the only
8545
- * operator-facing way to name a stream: a cam-stream id is broker-internal and
8546
- * moves when a slot is reassigned, so a stored id silently stops meaning what
8547
- * the operator picked. Today's one user is the audio analyser, whose rule is
8548
- * "the lowest AVAILABLE native rung" (D537).
8549
- */
8550
- var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
8551
- var CamStreamKindSchema = _enum([
8552
- "pull-rtsp",
8553
- "pull-rtmp",
8554
- "pull-http",
8555
- "pull-flv",
8556
- "pull-rfc4571",
8557
- "push-annexb",
8558
- "derived"
8559
- ]);
8560
- var CamStreamResolutionSchema = object({
8561
- width: number().int().positive().meta(readingMeta("none")),
8562
- height: number().int().positive().meta(readingMeta("none"))
8563
- });
8564
- var CameraStreamSchema = object({
8565
- /** Stable, provider-assigned id unique within the (deviceId) scope. */
8566
- camStreamId: string().min(1),
8567
- deviceId: number().int().nonnegative(),
8568
- kind: CamStreamKindSchema,
8569
- /** Required for pull-* kinds. Ignored for push-annexb. */
8570
- url: string().optional(),
8571
- codec: string().optional(),
8572
- resolution: CamStreamResolutionSchema.optional(),
8573
- fps: number().positive().optional(),
8574
- /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
8575
- label: string().optional(),
8576
- /**
8577
- * Device-level features the publisher advertised (e.g. `battery-operated`).
8578
- * The broker, snapshot orchestrator, and prebuffer manager all consult
8579
- * this list to derive policy — relaxed stall watchdog for battery
8580
- * cams, prebuffer off by default, longer snapshot rate-limit, etc.
8581
- *
8582
- * Single source of truth replacing per-stream flags like the
8583
- * historical `allowStall`: if the publisher knows the camera is
8584
- * battery-powered, every downstream service derives the right policy
8585
- * from this list.
8586
- */
8587
- deviceFeatures: array(string()).optional(),
8588
- /**
8589
- * Whether this stream participates in the broker's automatic profile
8590
- * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
8591
- * use `false` when they want a stream to be SELECTABLE in the UI but not
8592
- * picked by default — e.g. Reolink publishes its native Baichuan streams
8593
- * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
8594
- * mirrors as `autoEligible: false` (still pickable per slot, just not
8595
- * the auto choice). Manual `assignProfile` calls remain valid for
8596
- * non-eligible streams.
8597
- */
8598
- autoEligible: boolean().optional(),
8599
- /**
8600
- * The profile slot the PUBLISHER says this stream is: `low` for a stream
8601
- * whose only sensible role is the low-bandwidth one, `high` for a main
8602
- * stream. Consulted by the broker's automatic ranking
8603
- * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
8604
- * an operator's manual assignment still wins, and an absent hint is the
8605
- * pixel-ranked default every camera had before this field existed.
8606
- *
8607
- * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
8608
- * storage class is written for on its own (`recordings` is high|mid,
8609
- * `recordingsLow` is low) and the one profile scrub does not read. The robot
8610
- * camera's single 720p relay is a `low` in every respect except the slot the
8611
- * heuristic put it in.
8612
- */
8613
- profileHint: CamProfileSchema.optional(),
8614
- /**
8615
- * Transport-specific opaque metadata. The broker passes it through to
8616
- * the source reader without inspecting it. Currently used by
8617
- * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
8618
- * route RTP packets to the right depacketizer without an in-band
8619
- * DESCRIBE phase). Other kinds typically leave it undefined.
8620
- */
8621
- metadata: record(string(), unknown()).optional()
8622
- });
8623
- var ProfileSlotStatusSchema = _enum([
8624
- "unassigned",
8625
- "idle",
8626
- "connecting",
8627
- "streaming",
8628
- "error"
8629
- ]);
8630
- var ProfileSlotSchema = object({
8631
- deviceId: number().int().nonnegative(),
8632
- profile: CamProfileSchema,
8633
- /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
8634
- brokerId: string(),
8635
- /** `null` when the profile is unassigned. */
8636
- sourceCamStreamId: string().nullable(),
8637
- status: ProfileSlotStatusSchema,
8638
- resolution: CamStreamResolutionSchema.optional(),
8639
- codec: string().optional(),
8640
- preBufferSec: number().nonnegative().optional(),
8641
- errorMessage: string().optional()
8642
- });
8643
- /**
8644
- * Zod schema for StreamSourceEntry — the canonical stream descriptor
8645
- * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
8646
- */
8647
- var StreamSourceEntrySchema$1 = object({
8648
- id: string(),
8649
- label: string(),
8650
- protocol: _enum([
8651
- "rtsp",
8652
- "rtmp",
8653
- "annexb",
8654
- "http-mjpeg",
8655
- "webrtc",
8656
- "custom"
8657
- ]),
8658
- url: string().optional(),
8659
- resolution: object({
8660
- width: number(),
8661
- height: number()
8662
- }).readonly().optional(),
8663
- fps: number().optional(),
8664
- bitrate: number().optional(),
8665
- codec: string().optional(),
8666
- profileHint: CamProfileSchema.optional()
8667
- });
8668
- object({
8669
- type: string(),
8670
- url: string(),
8671
- videoCodec: string().optional(),
8672
- audioCodec: string().optional(),
8673
- metadata: record(string(), unknown()).readonly().optional()
8674
- });
8675
- /**
8676
- * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
8677
- * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
8678
- * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
8679
- * (the runner's native-crop and pipeline-executor inputs) can carry handles
8680
- * over tRPC / Moleculer.
8681
- *
8682
- * The `satisfies` assertion below pins this schema to the interface: if a
8683
- * field is added to / removed from `FrameHandle` without a matching schema
8684
- * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
8685
- * `FrameHandle` and the build fails — no silent drift.
8686
- */
8687
- var FrameHandleSchema = object({
8688
- shmId: string(),
8689
- slot: number().int().nonnegative(),
8690
- seq: number().int().nonnegative(),
8691
- width: number().int().positive(),
8692
- height: number().int().positive(),
8693
- format: _enum([
8694
- "jpeg",
8695
- "rgb",
8696
- "bgr",
8697
- "yuv420",
8698
- "gray"
8699
- ]),
8700
- pts: number(),
8701
- byteLength: number().int().nonnegative(),
8702
- nodeId: string(),
8703
- slotCount: number().int().positive()
8704
- });
8705
- /**
8706
- * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
8707
- * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
8708
- * sample bytes plus the track parameters a consumer needs to interpret them.
8709
- *
8710
- * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
8711
- * Float32 PCM), so unlike video frames they ship their bytes INLINE over
8712
- * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
8713
- * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
8714
- * it.
8715
- */
8716
- var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
8717
- var DecodedAudioChunkSchema = object({
8718
- data: _instanceof(Uint8Array),
8719
- sampleRate: number().int().positive(),
8720
- channels: number().int().positive(),
8721
- timestamp: number(),
8722
- /**
8723
- * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
8724
- * byte, for any peer that never heard of this field. A coded window
8725
- * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
8726
- * subscription that DECLARED it accepts one, so absence can never mean
8727
- * "coded bytes a consumer will read as floats" (D455).
8728
- */
8729
- format: AudioChunkFormatSchema.optional()
8730
- });
8731
- /**
8732
- * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
8733
- * its bytes inline: a consumer asks the broker for the decoded audio-chunk
8734
- * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
8735
- * the consumer drains via `pullAudioChunks`.
8736
- */
8737
- var SubscribeAudioChunksInputSchema = object({
8738
- brokerId: string(),
8739
- /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
8740
- tag: string().optional(),
8741
- /**
8742
- * Byte formats this subscriber can READ, best first. The broker serves the
8743
- * chunk's own format when it is in this list and expands to `f32le`
8744
- * otherwise, so a subscriber is never handed bytes it cannot interpret.
8745
- *
8746
- * Absent (or without the source format) means `f32le` — the behaviour every
8747
- * subscriber had before D455, unchanged. This is the negotiation half of
8748
- * the source-bytes lever: it is what lets the broker and its consumers
8749
- * deploy one at a time across three nodes.
8750
- */
8751
- accept: array(AudioChunkFormatSchema).readonly().optional()
8752
- });
8753
- /** Result of `stream-broker.subscribeAudioChunks`. */
8754
- var SubscribeAudioChunksResultSchema = object({
8755
- /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
8756
- subscriptionId: string() });
8757
- var BrokerStatsSchema = object({
8758
- status: _enum([
8759
- "idle",
8760
- "connecting",
8761
- "streaming",
8762
- "error",
8763
- "stopped"
8764
- ]),
8765
- inputFps: number(),
8766
- encodedSubscribers: number(),
8767
- uptimeMs: number(),
8768
- bitrateKbps: number(),
8769
- idrIntervalMs: number(),
8770
- codec: string().optional(),
8771
- totalBytes: number(),
8772
- packetCount: number(),
8773
- rtspClients: number(),
8774
- pipeClients: number(),
8775
- preBufferSec: number(),
8776
- preBufferMs: number(),
8777
- preBufferPackets: number(),
8778
- /**
8779
- * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
8780
- * when the stream has no audio track or the broker is in cold start.
8781
- * `supported = false` means the codec was detected but the local
8782
- * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
8783
- * AAC pipeline wired). Surfaced in the UI device-overview so operators
8784
- * can pre-pick the audio-analysis model that matches the codec.
8785
- */
8786
- audio: object({
8787
- codec: string(),
8788
- sampleRate: number(),
8789
- channels: number(),
8790
- supported: boolean()
8791
- }).nullable().optional(),
8792
- /**
8793
- * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
8794
- * distributing none of the device's audio, on live or recording.
8795
- *
8796
- * Present so a silent camera can be told apart from a broken one on the
8797
- * stream panel itself, without cross-referencing the switch group: a
8798
- * broker holding an `audio` track descriptor while `audioMuted` is true is
8799
- * working exactly as asked. `audioMutedDropped` counts the audio units
8800
- * thrown away since the current dial — it is how you confirm from stats
8801
- * alone that the mute is on the packet path and not merely persisted.
8802
- */
8803
- audioMuted: boolean().optional(),
8804
- audioMutedDropped: number().optional()
8805
- });
8806
- /**
8807
- * Exporter-facing "profile restream" entry. Returned by
8808
- * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
8809
- * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
8810
- * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
8811
- * RTSP restream that aliases the profile's assigned source broker, so
8812
- * every consumer dialling a profile converges on the same single pull.
8813
- * `codec`/`resolution` describe the assigned SOURCE camStream (for the
8814
- * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
8815
- * (raw per-camStream, live-view only).
8816
- */
8817
- var ProfileRtspEntrySchema = object({
8818
- profile: CamProfileSchema,
8819
- /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
8820
- brokerId: string(),
8821
- url: string(),
8822
- mutedUrl: string(),
8823
- enabled: boolean(),
8824
- codec: string().optional(),
8825
- resolution: CamStreamResolutionSchema.optional()
8826
- });
8827
- /**
8828
- * The model weight formats a catalog entry can ship. Schema-free on purpose:
8829
- * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
8830
- * (the model downloader in `@camstack/system`) needs only the list and must
8831
- * reach it through `@camstack/types/addon` without building the cap schema
8832
- * graph (D28).
8833
- */
8834
- var MODEL_FORMATS = [
8835
- "onnx",
8836
- "coreml",
8837
- "openvino",
8838
- "tflite",
8839
- "pt",
8840
- "gguf"
8841
- ];
8842
8952
  object({
8843
8953
  field: string().min(1),
8844
8954
  on: literal("advance")
@@ -8931,6 +9041,410 @@ function optionalMethod(input, output, options) {
8931
9041
  function event(data) {
8932
9042
  return { data };
8933
9043
  }
9044
+ var READING_QUANTITIES = [
9045
+ "temperature",
9046
+ "pressure",
9047
+ "speed",
9048
+ "precipitation",
9049
+ "precipitation-rate",
9050
+ "length",
9051
+ "area",
9052
+ "illuminance",
9053
+ "irradiance",
9054
+ "power",
9055
+ "energy",
9056
+ "voltage",
9057
+ "current",
9058
+ "apparent-power",
9059
+ "humidity",
9060
+ "percentage",
9061
+ "concentration-volume",
9062
+ "concentration-mass",
9063
+ "concentration-ppb",
9064
+ "volume",
9065
+ "duration",
9066
+ "duration-ms",
9067
+ "information",
9068
+ "sound-level",
9069
+ "signal-level",
9070
+ "colour-temperature",
9071
+ "angle",
9072
+ "bitrate",
9073
+ "rate",
9074
+ "mass",
9075
+ "index",
9076
+ "ratio",
9077
+ "count",
9078
+ "none",
9079
+ "device"
9080
+ ];
9081
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
9082
+ var QUANTITY_SET = new Set(READING_QUANTITIES);
9083
+ function isReadingQuantity(value) {
9084
+ return typeof value === "string" && QUANTITY_SET.has(value);
9085
+ }
9086
+ /**
9087
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
9088
+ * (or an index / ratio / count). `device` means the cap has no single unit.
9089
+ */
9090
+ var QUANTITY_BASE = {
9091
+ temperature: "°C",
9092
+ pressure: "hPa",
9093
+ speed: "m/s",
9094
+ precipitation: "mm",
9095
+ "precipitation-rate": "mm/h",
9096
+ length: "m",
9097
+ area: "m²",
9098
+ illuminance: "lx",
9099
+ irradiance: "W/m²",
9100
+ power: "W",
9101
+ energy: "kWh",
9102
+ voltage: "V",
9103
+ current: "A",
9104
+ "apparent-power": "VA",
9105
+ humidity: "%",
9106
+ percentage: "%",
9107
+ "concentration-volume": "ppm",
9108
+ "concentration-mass": "µg/m³",
9109
+ "concentration-ppb": "ppb",
9110
+ volume: "L",
9111
+ duration: "s",
9112
+ "duration-ms": "ms",
9113
+ information: "B",
9114
+ "sound-level": "dBFS",
9115
+ "signal-level": "dBm",
9116
+ "colour-temperature": "mired",
9117
+ angle: "°",
9118
+ bitrate: "kbps",
9119
+ rate: "fps",
9120
+ mass: "kg",
9121
+ index: "none",
9122
+ ratio: "none",
9123
+ count: "none",
9124
+ none: "none",
9125
+ device: "device"
9126
+ };
9127
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
9128
+ function baseUnitOf(quantity) {
9129
+ const symbol = QUANTITY_BASE[quantity];
9130
+ if (symbol === "none" || symbol === "device") return void 0;
9131
+ return symbol;
9132
+ }
9133
+ function readingMeta(quantity, extras) {
9134
+ return {
9135
+ quantity,
9136
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
9137
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
9138
+ };
9139
+ }
9140
+ /** Shared Zod schemas used across streaming capabilities. */
9141
+ var CamProfileSchema = _enum([
9142
+ "high",
9143
+ "mid",
9144
+ "low"
9145
+ ]);
9146
+ /**
9147
+ * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
9148
+ *
9149
+ * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
9150
+ * that consumer owns". It lives beside `CamProfile` because a rung is the only
9151
+ * operator-facing way to name a stream: a cam-stream id is broker-internal and
9152
+ * moves when a slot is reassigned, so a stored id silently stops meaning what
9153
+ * the operator picked. Today's one user is the audio analyser, whose rule is
9154
+ * "the lowest AVAILABLE native rung" (D537).
9155
+ */
9156
+ var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
9157
+ var CamStreamKindSchema = _enum([
9158
+ "pull-rtsp",
9159
+ "pull-rtmp",
9160
+ "pull-http",
9161
+ "pull-flv",
9162
+ "pull-rfc4571",
9163
+ "push-annexb",
9164
+ "derived"
9165
+ ]);
9166
+ var CamStreamResolutionSchema = object({
9167
+ width: number().int().positive().meta(readingMeta("none")),
9168
+ height: number().int().positive().meta(readingMeta("none"))
9169
+ });
9170
+ var CameraStreamSchema = object({
9171
+ /** Stable, provider-assigned id unique within the (deviceId) scope. */
9172
+ camStreamId: string().min(1),
9173
+ deviceId: number().int().nonnegative(),
9174
+ kind: CamStreamKindSchema,
9175
+ /** Required for pull-* kinds. Ignored for push-annexb. */
9176
+ url: string().optional(),
9177
+ codec: string().optional(),
9178
+ resolution: CamStreamResolutionSchema.optional(),
9179
+ fps: number().positive().optional(),
9180
+ /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
9181
+ label: string().optional(),
9182
+ /**
9183
+ * Device-level features the publisher advertised (e.g. `battery-operated`).
9184
+ * The broker, snapshot orchestrator, and prebuffer manager all consult
9185
+ * this list to derive policy — relaxed stall watchdog for battery
9186
+ * cams, prebuffer off by default, longer snapshot rate-limit, etc.
9187
+ *
9188
+ * Single source of truth replacing per-stream flags like the
9189
+ * historical `allowStall`: if the publisher knows the camera is
9190
+ * battery-powered, every downstream service derives the right policy
9191
+ * from this list.
9192
+ */
9193
+ deviceFeatures: array(string()).optional(),
9194
+ /**
9195
+ * Whether this stream participates in the broker's automatic profile
9196
+ * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
9197
+ * use `false` when they want a stream to be SELECTABLE in the UI but not
9198
+ * picked by default — e.g. Reolink publishes its native Baichuan streams
9199
+ * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
9200
+ * mirrors as `autoEligible: false` (still pickable per slot, just not
9201
+ * the auto choice). Manual `assignProfile` calls remain valid for
9202
+ * non-eligible streams.
9203
+ */
9204
+ autoEligible: boolean().optional(),
9205
+ /**
9206
+ * The profile slot the PUBLISHER says this stream is: `low` for a stream
9207
+ * whose only sensible role is the low-bandwidth one, `high` for a main
9208
+ * stream. Consulted by the broker's automatic ranking
9209
+ * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
9210
+ * an operator's manual assignment still wins, and an absent hint is the
9211
+ * pixel-ranked default every camera had before this field existed.
9212
+ *
9213
+ * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
9214
+ * storage class is written for on its own (`recordings` is high|mid,
9215
+ * `recordingsLow` is low) and the one profile scrub does not read. The robot
9216
+ * camera's single 720p relay is a `low` in every respect except the slot the
9217
+ * heuristic put it in.
9218
+ */
9219
+ profileHint: CamProfileSchema.optional(),
9220
+ /**
9221
+ * Transport-specific opaque metadata. The broker passes it through to
9222
+ * the source reader without inspecting it. Currently used by
9223
+ * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
9224
+ * route RTP packets to the right depacketizer without an in-band
9225
+ * DESCRIBE phase). Other kinds typically leave it undefined.
9226
+ */
9227
+ metadata: record(string(), unknown()).optional()
9228
+ });
9229
+ var ProfileSlotStatusSchema = _enum([
9230
+ "unassigned",
9231
+ "idle",
9232
+ "connecting",
9233
+ "streaming",
9234
+ "error"
9235
+ ]);
9236
+ var ProfileSlotSchema = object({
9237
+ deviceId: number().int().nonnegative(),
9238
+ profile: CamProfileSchema,
9239
+ /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
9240
+ brokerId: string(),
9241
+ /** `null` when the profile is unassigned. */
9242
+ sourceCamStreamId: string().nullable(),
9243
+ status: ProfileSlotStatusSchema,
9244
+ resolution: CamStreamResolutionSchema.optional(),
9245
+ codec: string().optional(),
9246
+ preBufferSec: number().nonnegative().optional(),
9247
+ errorMessage: string().optional()
9248
+ });
9249
+ /**
9250
+ * Zod schema for StreamSourceEntry — the canonical stream descriptor
9251
+ * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
9252
+ */
9253
+ var StreamSourceEntrySchema$1 = object({
9254
+ id: string(),
9255
+ label: string(),
9256
+ protocol: _enum([
9257
+ "rtsp",
9258
+ "rtmp",
9259
+ "annexb",
9260
+ "http-mjpeg",
9261
+ "webrtc",
9262
+ "custom"
9263
+ ]),
9264
+ url: string().optional(),
9265
+ resolution: object({
9266
+ width: number(),
9267
+ height: number()
9268
+ }).readonly().optional(),
9269
+ fps: number().optional(),
9270
+ bitrate: number().optional(),
9271
+ codec: string().optional(),
9272
+ profileHint: CamProfileSchema.optional()
9273
+ });
9274
+ object({
9275
+ type: string(),
9276
+ url: string(),
9277
+ videoCodec: string().optional(),
9278
+ audioCodec: string().optional(),
9279
+ metadata: record(string(), unknown()).readonly().optional()
9280
+ });
9281
+ /**
9282
+ * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
9283
+ * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
9284
+ * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
9285
+ * (the runner's native-crop and pipeline-executor inputs) can carry handles
9286
+ * over tRPC / Moleculer.
9287
+ *
9288
+ * The `satisfies` assertion below pins this schema to the interface: if a
9289
+ * field is added to / removed from `FrameHandle` without a matching schema
9290
+ * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
9291
+ * `FrameHandle` and the build fails — no silent drift.
9292
+ */
9293
+ var FrameHandleSchema = object({
9294
+ shmId: string(),
9295
+ slot: number().int().nonnegative(),
9296
+ seq: number().int().nonnegative(),
9297
+ width: number().int().positive(),
9298
+ height: number().int().positive(),
9299
+ format: _enum([
9300
+ "jpeg",
9301
+ "rgb",
9302
+ "bgr",
9303
+ "yuv420",
9304
+ "gray"
9305
+ ]),
9306
+ pts: number(),
9307
+ byteLength: number().int().nonnegative(),
9308
+ nodeId: string(),
9309
+ slotCount: number().int().positive()
9310
+ });
9311
+ /**
9312
+ * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
9313
+ * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
9314
+ * sample bytes plus the track parameters a consumer needs to interpret them.
9315
+ *
9316
+ * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
9317
+ * Float32 PCM), so unlike video frames they ship their bytes INLINE over
9318
+ * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
9319
+ * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
9320
+ * it.
9321
+ */
9322
+ var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
9323
+ var DecodedAudioChunkSchema = object({
9324
+ data: _instanceof(Uint8Array),
9325
+ sampleRate: number().int().positive(),
9326
+ channels: number().int().positive(),
9327
+ timestamp: number(),
9328
+ /**
9329
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
9330
+ * byte, for any peer that never heard of this field. A coded window
9331
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
9332
+ * subscription that DECLARED it accepts one, so absence can never mean
9333
+ * "coded bytes a consumer will read as floats" (D455).
9334
+ */
9335
+ format: AudioChunkFormatSchema.optional()
9336
+ });
9337
+ /**
9338
+ * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
9339
+ * its bytes inline: a consumer asks the broker for the decoded audio-chunk
9340
+ * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
9341
+ * the consumer drains via `pullAudioChunks`.
9342
+ */
9343
+ var SubscribeAudioChunksInputSchema = object({
9344
+ brokerId: string(),
9345
+ /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
9346
+ tag: string().optional(),
9347
+ /**
9348
+ * Byte formats this subscriber can READ, best first. The broker serves the
9349
+ * chunk's own format when it is in this list and expands to `f32le`
9350
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
9351
+ *
9352
+ * Absent (or without the source format) means `f32le` — the behaviour every
9353
+ * subscriber had before D455, unchanged. This is the negotiation half of
9354
+ * the source-bytes lever: it is what lets the broker and its consumers
9355
+ * deploy one at a time across three nodes.
9356
+ */
9357
+ accept: array(AudioChunkFormatSchema).readonly().optional()
9358
+ });
9359
+ /** Result of `stream-broker.subscribeAudioChunks`. */
9360
+ var SubscribeAudioChunksResultSchema = object({
9361
+ /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
9362
+ subscriptionId: string() });
9363
+ var BrokerStatsSchema = object({
9364
+ status: _enum([
9365
+ "idle",
9366
+ "connecting",
9367
+ "streaming",
9368
+ "error",
9369
+ "stopped"
9370
+ ]),
9371
+ inputFps: number(),
9372
+ encodedSubscribers: number(),
9373
+ uptimeMs: number(),
9374
+ bitrateKbps: number(),
9375
+ idrIntervalMs: number(),
9376
+ codec: string().optional(),
9377
+ totalBytes: number(),
9378
+ packetCount: number(),
9379
+ rtspClients: number(),
9380
+ pipeClients: number(),
9381
+ preBufferSec: number(),
9382
+ preBufferMs: number(),
9383
+ preBufferPackets: number(),
9384
+ /**
9385
+ * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
9386
+ * when the stream has no audio track or the broker is in cold start.
9387
+ * `supported = false` means the codec was detected but the local
9388
+ * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
9389
+ * AAC pipeline wired). Surfaced in the UI device-overview so operators
9390
+ * can pre-pick the audio-analysis model that matches the codec.
9391
+ */
9392
+ audio: object({
9393
+ codec: string(),
9394
+ sampleRate: number(),
9395
+ channels: number(),
9396
+ supported: boolean()
9397
+ }).nullable().optional(),
9398
+ /**
9399
+ * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
9400
+ * distributing none of the device's audio, on live or recording.
9401
+ *
9402
+ * Present so a silent camera can be told apart from a broken one on the
9403
+ * stream panel itself, without cross-referencing the switch group: a
9404
+ * broker holding an `audio` track descriptor while `audioMuted` is true is
9405
+ * working exactly as asked. `audioMutedDropped` counts the audio units
9406
+ * thrown away since the current dial — it is how you confirm from stats
9407
+ * alone that the mute is on the packet path and not merely persisted.
9408
+ */
9409
+ audioMuted: boolean().optional(),
9410
+ audioMutedDropped: number().optional()
9411
+ });
9412
+ /**
9413
+ * Exporter-facing "profile restream" entry. Returned by
9414
+ * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
9415
+ * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
9416
+ * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
9417
+ * RTSP restream that aliases the profile's assigned source broker, so
9418
+ * every consumer dialling a profile converges on the same single pull.
9419
+ * `codec`/`resolution` describe the assigned SOURCE camStream (for the
9420
+ * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
9421
+ * (raw per-camStream, live-view only).
9422
+ */
9423
+ var ProfileRtspEntrySchema = object({
9424
+ profile: CamProfileSchema,
9425
+ /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
9426
+ brokerId: string(),
9427
+ url: string(),
9428
+ mutedUrl: string(),
9429
+ enabled: boolean(),
9430
+ codec: string().optional(),
9431
+ resolution: CamStreamResolutionSchema.optional()
9432
+ });
9433
+ /**
9434
+ * The model weight formats a catalog entry can ship. Schema-free on purpose:
9435
+ * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
9436
+ * (the model downloader in `@camstack/server/system`) needs only the list and must
9437
+ * reach it through `@camstack/types/addon` without building the cap schema
9438
+ * graph (D28).
9439
+ */
9440
+ var MODEL_FORMATS = [
9441
+ "onnx",
9442
+ "coreml",
9443
+ "openvino",
9444
+ "tflite",
9445
+ "pt",
9446
+ "gguf"
9447
+ ];
8934
9448
  /**
8935
9449
  * Per-call node pinning for `ctx.api` capability calls.
8936
9450
  *
@@ -9231,6 +9745,7 @@ var CAP_NAMES = Object.freeze([
9231
9745
  "mesh-network",
9232
9746
  "meter",
9233
9747
  "metrics-provider",
9748
+ "migrations",
9234
9749
  "model-convert",
9235
9750
  "model-distributor",
9236
9751
  "motion",
@@ -9263,6 +9778,7 @@ var CAP_NAMES = Object.freeze([
9263
9778
  "plate-gallery",
9264
9779
  "platform-probe",
9265
9780
  "power-meter",
9781
+ "power-source",
9266
9782
  "presence",
9267
9783
  "pressure-sensor",
9268
9784
  "privacy-mask",
@@ -10614,6 +11130,13 @@ Object.freeze({
10614
11130
  "valueType": "number"
10615
11131
  }
10616
11132
  ],
11133
+ "power-source": [{
11134
+ "path": "switchOn",
11135
+ "valueType": "boolean"
11136
+ }, {
11137
+ "path": "offSeconds",
11138
+ "valueType": "number"
11139
+ }],
10617
11140
  "presence": [
10618
11141
  {
10619
11142
  "path": "state",
@@ -11125,379 +11648,7 @@ object({
11125
11648
  params: record(string(), union([string(), number()])).optional()
11126
11649
  });
11127
11650
  //#endregion
11128
- //#region ../types/dist/err-msg-BD5buxvT.mjs
11129
- var DeviceType = /* @__PURE__ */ function(DeviceType) {
11130
- DeviceType["Camera"] = "camera";
11131
- DeviceType["Hub"] = "hub";
11132
- DeviceType["Light"] = "light";
11133
- DeviceType["Siren"] = "siren";
11134
- DeviceType["Switch"] = "switch";
11135
- DeviceType["Sensor"] = "sensor";
11136
- DeviceType["Thermostat"] = "thermostat";
11137
- /** Air-conditioner / heat-pump climate device (HVAC) — shares the
11138
- * `climate-control` cap surface with `Thermostat` but renders a
11139
- * dedicated AC-appropriate control UI (mode chips, fan speed,
11140
- * independent vertical/horizontal swing). Sources: native Gree, and
11141
- * reusable by other AC integrations. */
11142
- DeviceType["Climate"] = "climate";
11143
- DeviceType["Button"] = "button";
11144
- /** Generic stateless event emitter — carries a device's EXACT declared
11145
- * event vocabulary verbatim (no normalization). Installed with the
11146
- * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
11147
- * HA bus events (e.g. `zha_event`, generic). */
11148
- DeviceType["EventEmitter"] = "event-emitter";
11149
- /** Firmware/software update entity — current vs available version,
11150
- * updatable flag, update state, and an install action. Installed with
11151
- * the `update` cap. Sources: Homematic firmware-update channels (and
11152
- * reusable by other providers, e.g. HA `update.*` entities). */
11153
- DeviceType["Update"] = "update";
11154
- DeviceType["Generic"] = "generic";
11155
- /** Generic notification delivery target (HA `notify.<service>`, future
11156
- * Telegram / Discord / ntfy / SMTP, …). One device per delivery
11157
- * endpoint; the `notifier` cap defines the send surface. */
11158
- DeviceType["Notifier"] = "notifier";
11159
- /** Pre-recorded action sequence with optional parameters
11160
- * (HA `script.*`). Runnable via `script-runner` cap. */
11161
- DeviceType["Script"] = "script";
11162
- /** Automation rule (HA `automation.*`) — enable/disable + manual
11163
- * trigger surface exposed via `automation-control` cap. */
11164
- DeviceType["Automation"] = "automation";
11165
- /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
11166
- DeviceType["Lock"] = "lock";
11167
- /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
11168
- * `valve.*`). `cover` cap with sub-roles for variant. */
11169
- DeviceType["Cover"] = "cover";
11170
- /** Pipe / water / gas valve with open/close/stop and optional
11171
- * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
11172
- * modelled on the same open/closed lifecycle. */
11173
- DeviceType["Valve"] = "valve";
11174
- /** Humidifier / dehumidifier with on/off + target humidity + mode
11175
- * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
11176
- * modelled on the same target / mode lifecycle. */
11177
- DeviceType["Humidifier"] = "humidifier";
11178
- /** Water heater / boiler with target temperature + operation mode +
11179
- * away mode (HA `water_heater.*`). `water-heater` cap — a
11180
- * climate-family actuator. */
11181
- DeviceType["WaterHeater"] = "water-heater";
11182
- /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
11183
- DeviceType["Fan"] = "fan";
11184
- /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
11185
- * the camera surface — those use `Camera`. `media-player` cap. */
11186
- DeviceType["MediaPlayer"] = "media-player";
11187
- /** Security panel / alarm system (HA `alarm_control_panel.*`).
11188
- * `alarm-panel` cap. */
11189
- DeviceType["AlarmPanel"] = "alarm-panel";
11190
- /** Generic user-settable input (HA `number` / `input_number` / `select`
11191
- * / `input_select` / `text` / `input_text` / `input_datetime`).
11192
- * Sub-type via `DeviceRole`: NumericControl / SelectControl /
11193
- * TextControl / DateTimeControl. */
11194
- DeviceType["Control"] = "control";
11195
- /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
11196
- * `presence` cap. */
11197
- DeviceType["Presence"] = "presence";
11198
- /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
11199
- * `weather` cap. */
11200
- DeviceType["Weather"] = "weather";
11201
- /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
11202
- DeviceType["Vacuum"] = "vacuum";
11203
- /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
11204
- * `lawn-mower-control` cap. */
11205
- DeviceType["LawnMower"] = "lawn-mower";
11206
- /** Physical HA device group — parent container for entity-children
11207
- * adopted from a single HA device entry. Not renderable as a
11208
- * standalone device; exists only to anchor child entities. */
11209
- DeviceType["Container"] = "container";
11210
- /** Single still-image entity (HA `image.*`). Read-only display of an
11211
- * `entity_picture` signed URL the browser loads directly. `image` cap. */
11212
- DeviceType["Image"] = "image";
11213
- /** Smart pet feeder — cloud-connected food dispenser with a bowl food
11214
- * level, battery, desiccant life, feeding state and manual-feed /
11215
- * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
11216
- * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
11217
- * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
11218
- * integrations sharing the same food/desiccant/hopper surface. */
11219
- DeviceType["PetFeeder"] = "pet-feeder";
11220
- /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
11221
- * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
11222
- * of a container and is BUILT as a composed device (D967). */
11223
- DeviceType["PetFountain"] = "pet-fountain";
11224
- /** Built-in dishwasher — program lifecycle, door / remote-start gates,
11225
- * salt and rinse-aid levels. Installed with the `dishwasher` cap.
11226
- * Sources: native Bosch Home Connect, reusable by similar integrations.
11227
- * A plug-built dishwasher carries running-state and power instead. */
11228
- DeviceType["Dishwasher"] = "dishwasher";
11229
- /** Washing machine. A plug-built one is running while its power stays
11230
- * above a threshold. No program cap — that surface is the dishwasher's. */
11231
- DeviceType["Washer"] = "washer";
11232
- /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11233
- DeviceType["Dryer"] = "dryer";
11234
- /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
11235
- * other radio-to-IP bridges). It carries the box's own facts (firmware,
11236
- * address, connection state) and the actions the box really supports
11237
- * (`reboot`, `device-admin-link`). Products are NOT its children — they are
11238
- * adopted beside it — and its connection settings stay on the integration. */
11239
- DeviceType["Gateway"] = "gateway";
11240
- return DeviceType;
11241
- }({});
11242
- var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
11243
- DeviceFeature["BatteryOperated"] = "battery-operated";
11244
- DeviceFeature["Rebootable"] = "rebootable";
11245
- /**
11246
- * Device supports an on-demand re-sync of its derived spec with its
11247
- * upstream source — drives the generic Re-sync button. The owning
11248
- * provider implements the action via the `device-adoption.resync` cap.
11249
- */
11250
- DeviceFeature["Resyncable"] = "resyncable";
11251
- DeviceFeature["NativeSnapshot"] = "native-snapshot";
11252
- DeviceFeature["DoorbellButton"] = "doorbell-button";
11253
- DeviceFeature["TwoWayAudio"] = "two-way-audio";
11254
- DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
11255
- /**
11256
- * Camera supports the on-firmware autotrack subsystem (subject-
11257
- * following). Distinct from `PanTiltZoom` because not every PTZ
11258
- * camera ships autotrack — the admin UI uses this flag to gate
11259
- * the autotrack toggle / settings card without re-deriving from
11260
- * the cap registry. Mirrors `ptz-autotrack` cap registration:
11261
- * driver sets this feature when probe confirms the firmware
11262
- * surface, and registers the cap in the same code path.
11263
- */
11264
- DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
11265
- /**
11266
- * Accessory exposes a "trigger on motion" toggle — the parent camera's
11267
- * motion detection automatically activates this device. Mirrors
11268
- * `motion-trigger` cap registration: drivers set this feature in the
11269
- * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
11270
- *
11271
- * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
11272
- * fast scalar without binding fetch), notifier rules, and `listAll`
11273
- * filters that want "all devices with on-motion behaviour".
11274
- */
11275
- DeviceFeature["MotionTrigger"] = "motion-trigger";
11276
- /** Light supports rgb-triplet color via `color` cap. */
11277
- DeviceFeature["LightColorRgb"] = "light-color-rgb";
11278
- /** Light supports HSV color via `color` cap. */
11279
- DeviceFeature["LightColorHsv"] = "light-color-hsv";
11280
- /** Light supports color-temperature (mired) via `color` cap. */
11281
- DeviceFeature["LightColorMired"] = "light-color-mired";
11282
- /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
11283
- * targetHigh). Gates the range slider UI. */
11284
- DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
11285
- /** Thermostat exposes target humidity and/or current humidity
11286
- * readings. Gates the humidity controls. */
11287
- DeviceFeature["ClimateHumidity"] = "climate-humidity";
11288
- /** Thermostat exposes a fan-mode selector. */
11289
- DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
11290
- /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
11291
- DeviceFeature["ClimatePreset"] = "climate-preset";
11292
- /** Thermostat exposes a vertical louver swing toggle. Gates the
11293
- * vertical-swing switch in the climate UI. Independent of horizontal. */
11294
- DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
11295
- /** Thermostat exposes a horizontal louver swing toggle. Gates the
11296
- * horizontal-swing switch in the climate UI. Independent of vertical. */
11297
- DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
11298
- /** Cover exposes intermediate position control (0..100). Gates the
11299
- * position slider UI. */
11300
- DeviceFeature["CoverPositionable"] = "cover-positionable";
11301
- /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
11302
- DeviceFeature["CoverTilt"] = "cover-tilt";
11303
- /** Valve exposes intermediate position control (0..100). Gates the
11304
- * position slider / drag surface UI. */
11305
- DeviceFeature["ValvePositionable"] = "valve-positionable";
11306
- /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
11307
- DeviceFeature["FanSpeed"] = "fan-speed";
11308
- /** Fan exposes a preset mode selector. */
11309
- DeviceFeature["FanPreset"] = "fan-preset";
11310
- /** Fan exposes blade direction (forward/reverse) — typical of
11311
- * ceiling fans. */
11312
- DeviceFeature["FanDirection"] = "fan-direction";
11313
- /** Fan exposes an oscillation toggle. */
11314
- DeviceFeature["FanOscillating"] = "fan-oscillating";
11315
- /** Lock requires a PIN code on lock/unlock. Gates the code-entry
11316
- * field on the UI lock-controls panel. */
11317
- DeviceFeature["LockPinRequired"] = "lock-pin-required";
11318
- /** Lock supports a latch-release ("open door") action distinct from
11319
- * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
11320
- * `supported_features`. Gates the Open Door button in the UI. */
11321
- DeviceFeature["LockOpen"] = "lock-open";
11322
- /** Media player exposes a seek-to-position surface. */
11323
- DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
11324
- /** Media player exposes a volume-level setter. */
11325
- DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
11326
- /** Media player exposes a mute toggle distinct from volume=0. */
11327
- DeviceFeature["MediaPlayerMute"] = "media-player-mute";
11328
- /** Media player exposes a shuffle toggle. */
11329
- DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
11330
- /** Media player exposes a repeat mode (off / all / one). */
11331
- DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
11332
- /** Media player exposes a source / input selector. */
11333
- DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
11334
- /** Media player exposes a play-arbitrary-media surface (URL / id). */
11335
- DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
11336
- /** Media player exposes next-track. */
11337
- DeviceFeature["MediaPlayerNext"] = "media-player-next";
11338
- /** Media player exposes previous-track. */
11339
- DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
11340
- /** Media player exposes stop distinct from pause. */
11341
- DeviceFeature["MediaPlayerStop"] = "media-player-stop";
11342
- /** Alarm panel requires a PIN code on arm/disarm. */
11343
- DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
11344
- /** Presence device carries GPS coordinates (lat/lng/accuracy) in
11345
- * addition to a textual location. */
11346
- DeviceFeature["PresenceGps"] = "presence-gps";
11347
- /** Notifier accepts an inline / URL image attachment. */
11348
- DeviceFeature["NotifierImage"] = "notifier-image";
11349
- /** Notifier accepts a priority hint (high/normal/low). */
11350
- DeviceFeature["NotifierPriority"] = "notifier-priority";
11351
- /** Notifier accepts a free-form `data` payload for platform-specific
11352
- * fields. */
11353
- DeviceFeature["NotifierData"] = "notifier-data";
11354
- /** Notifier supports interactive action buttons / callbacks. */
11355
- DeviceFeature["NotifierActions"] = "notifier-actions";
11356
- /** Notifier supports per-call recipient targeting (multi-user). */
11357
- DeviceFeature["NotifierRecipients"] = "notifier-recipients";
11358
- /** Script runner accepts a variables map on each run invocation. */
11359
- DeviceFeature["ScriptVariables"] = "script-variables";
11360
- /** Automation `trigger` accepts a skipCondition flag — fires the
11361
- * automation's actions while bypassing its condition block. */
11362
- DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
11363
- /** Robot vacuum exposes a live cleaning map (image child). Gates the
11364
- * map tile in the vacuum UI. */
11365
- DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
11366
- /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
11367
- * switches group. */
11368
- DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
11369
- /** Robot mower exposes a live mowing map (SVG image child). Gates the
11370
- * mower map tile. */
11371
- DeviceFeature["MowerHasMap"] = "mower-has-map";
11372
- /** Robot mower exposes targeted mowing (all-area / zones / edges /
11373
- * spots) — gates the Mow action + map selectors. */
11374
- DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
11375
- return DeviceFeature;
11376
- }({});
11377
- /**
11378
- * Semantic role a device plays within its parent. Populated by driver
11379
- * addons when creating accessory devices (Reolink siren/floodlight/
11380
- * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
11381
- * admin UI to pick icons, labels, and widgets — a `Switch` with
11382
- * `role: Floodlight` renders as a bulb with a brightness slider,
11383
- * whereas a `Switch` with `role: Siren` renders as a klaxon.
11384
- *
11385
- * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
11386
- * sqlite as a nullable TEXT column — old rows keep working unchanged.
11387
- */
11388
- var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11389
- DeviceRole["Siren"] = "siren";
11390
- DeviceRole["Floodlight"] = "floodlight";
11391
- DeviceRole["Spotlight"] = "spotlight";
11392
- DeviceRole["PirSensor"] = "pir-sensor";
11393
- DeviceRole["Chime"] = "chime";
11394
- DeviceRole["Autotrack"] = "autotrack";
11395
- DeviceRole["Nightvision"] = "nightvision";
11396
- DeviceRole["PrivacyMask"] = "privacy-mask";
11397
- DeviceRole["Doorbell"] = "doorbell";
11398
- /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
11399
- DeviceRole["CoverBlind"] = "cover-blind";
11400
- /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
11401
- DeviceRole["CoverShutter"] = "cover-shutter";
11402
- /** Exterior awning. HA `device_class: awning`. */
11403
- DeviceRole["CoverAwning"] = "cover-awning";
11404
- /** Curtain / drape. HA `device_class: curtain`. */
11405
- DeviceRole["CoverCurtain"] = "cover-curtain";
11406
- /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
11407
- DeviceRole["CoverGarage"] = "cover-garage";
11408
- /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
11409
- DeviceRole["CoverGate"] = "cover-gate";
11410
- /** Door. HA `device_class: door`. A security actuator: exports gate it. */
11411
- DeviceRole["CoverDoor"] = "cover-door";
11412
- /** Window. HA `device_class: window`. */
11413
- DeviceRole["CoverWindow"] = "cover-window";
11414
- /** Virtual HA toggle (input_boolean.*) — distinguishable from a
11415
- * real Switch device for UI rendering / export adapters. */
11416
- DeviceRole["BinaryHelper"] = "binary-helper";
11417
- /** A smart plug: a `switch` whose source says it is an outlet (HA
11418
- * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
11419
- DeviceRole["Outlet"] = "outlet";
11420
- /** Generic motion / occupancy / moving event source. Distinct from
11421
- * the camera accessory PirSensor role: that one is a camera child;
11422
- * this is a standalone HA / 3rd-party motion sensor. */
11423
- DeviceRole["MotionSensor"] = "motion-sensor";
11424
- DeviceRole["ContactSensor"] = "contact-sensor";
11425
- DeviceRole["LeakSensor"] = "leak-sensor";
11426
- DeviceRole["SmokeSensor"] = "smoke-sensor";
11427
- DeviceRole["COSensor"] = "co-sensor";
11428
- DeviceRole["GasSensor"] = "gas-sensor";
11429
- DeviceRole["TamperSensor"] = "tamper-sensor";
11430
- DeviceRole["VibrationSensor"] = "vibration-sensor";
11431
- DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
11432
- DeviceRole["SoundSensor"] = "sound-sensor";
11433
- /** Fallback for `binary_sensor` without a known `device_class`. */
11434
- DeviceRole["BinarySensor"] = "binary-sensor";
11435
- DeviceRole["TemperatureSensor"] = "temperature-sensor";
11436
- DeviceRole["HumiditySensor"] = "humidity-sensor";
11437
- DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
11438
- DeviceRole["PressureSensor"] = "pressure-sensor";
11439
- /** Wind speed or direction (weather-station `wind-sensor` cap). */
11440
- DeviceRole["WindSensor"] = "wind-sensor";
11441
- /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
11442
- DeviceRole["RainSensor"] = "rain-sensor";
11443
- /** UV index (weather-station `uv-sensor` cap). */
11444
- DeviceRole["UvSensor"] = "uv-sensor";
11445
- /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
11446
- * Distinct from AmbientLightSensor (lux). */
11447
- DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
11448
- /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
11449
- DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
11450
- DeviceRole["PowerSensor"] = "power-sensor";
11451
- DeviceRole["EnergySensor"] = "energy-sensor";
11452
- DeviceRole["VoltageSensor"] = "voltage-sensor";
11453
- DeviceRole["CurrentSensor"] = "current-sensor";
11454
- DeviceRole["AirQualitySensor"] = "air-quality-sensor";
11455
- /** Battery level (numeric % via `sensor` OR low-bool via
11456
- * `binary_sensor` — the cap distinguishes via the value type). */
11457
- DeviceRole["BatterySensor"] = "battery-sensor";
11458
- /** Fallback for `sensor` numeric without a known `device_class`. */
11459
- DeviceRole["NumericSensor"] = "numeric-sensor";
11460
- /** String / enum state (HA `sensor` with `state_class: enum` or
11461
- * `attributes.options`). */
11462
- DeviceRole["EnumSensor"] = "enum-sensor";
11463
- /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
11464
- * or `date`). The slice carries the raw ISO string verbatim (hosted on
11465
- * the `enum-sensor` cap); the UI renders it locale-formatted. */
11466
- DeviceRole["DateTimeSensor"] = "datetime-sensor";
11467
- /** Last-resort fallback when nothing else matches. */
11468
- DeviceRole["GenericSensor"] = "generic-sensor";
11469
- DeviceRole["NumericControl"] = "numeric-control";
11470
- DeviceRole["SelectControl"] = "select-control";
11471
- DeviceRole["TextControl"] = "text-control";
11472
- DeviceRole["DateTimeControl"] = "datetime-control";
11473
- /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
11474
- * rich features (image, priority, channel routing). */
11475
- DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
11476
- /** Chat / messaging service (HA `notify.telegram_*`,
11477
- * `notify.discord_*`, etc.). */
11478
- DeviceRole["MessagingNotifier"] = "messaging-notifier";
11479
- /** Email-based delivery (HA `notify.smtp`, etc.). */
11480
- DeviceRole["EmailNotifier"] = "email-notifier";
11481
- /** Fallback when the notifier service name doesn't match a known
11482
- * pattern. */
11483
- DeviceRole["GenericNotifier"] = "generic-notifier";
11484
- /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
11485
- * cap. A `DeviceType.Generic` device. */
11486
- DeviceRole["IrTransmitter"] = "ir-transmitter";
11487
- return DeviceRole;
11488
- }({});
11489
- /**
11490
- import { errMsg } from '@camstack/types'
11491
- * Extract a human-readable message from an unknown error value.
11492
- * Replaces the ubiquitous `errMsg(err)` pattern.
11493
- */
11494
- function errMsg(err) {
11495
- if (err instanceof Error) return err.message;
11496
- if (typeof err === "string") return err;
11497
- return String(err);
11498
- }
11499
- //#endregion
11500
- //#region ../types/dist/composition-C7WdThmP.mjs
11651
+ //#region ../types/dist/composition-BBzVf2xd.mjs
11501
11652
  /**
11502
11653
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11503
11654
  *
@@ -12716,6 +12867,90 @@ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
12716
12867
  var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
12717
12868
  new Set(EVENT_OWNER_TYPES);
12718
12869
  /**
12870
+ * What a published package needs from the rest of the install (D989).
12871
+ *
12872
+ * One published framework package (`@camstack/server`) carries the kernel, the
12873
+ * builtins and the capability contracts, so "which addon runs on which
12874
+ * framework" is a question of version, and the answer is DECLARED by the
12875
+ * package that knows it:
12876
+ *
12877
+ * - an addon says the oldest server it runs on (`requires.server: ">=X"`);
12878
+ * - the server says the oldest addon it works with
12879
+ * (`requires.addons: { "@camstack/addon-x": ">=Y" }`) and the oldest shell
12880
+ * around it (`requires.wrapper: { desktop, image }`);
12881
+ * - a wrapper (the desktop app, the container starter) says which server
12882
+ * range it can host, and reports its own kind and version.
12883
+ *
12884
+ * The publish workflow STAMPS these after the version bump
12885
+ * (`scripts/ci/stamp-requires.mjs`); nobody types a number by hand.
12886
+ *
12887
+ * A requirement that cannot be checked is NOT met: a shell that reports no
12888
+ * version counts as below the minimum, and an unreadable running version is
12889
+ * named in the result (a failed read is never a pass).
12890
+ */
12891
+ /** `>=MAJOR.MINOR.PATCH[-prerelease]` — the only range shape a requirement uses. */
12892
+ var MinVersionSchema = string().regex(/^>=\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, "a minimum is written \">=X.Y.Z\"");
12893
+ object({
12894
+ /** The oldest `@camstack/server` this package runs on. */
12895
+ server: MinVersionSchema.optional(),
12896
+ /** The oldest installed version of each named addon package. */
12897
+ addons: record(string(), MinVersionSchema).optional(),
12898
+ /** The oldest shell around the server, per shell kind. */
12899
+ wrapper: object({
12900
+ desktop: MinVersionSchema.optional(),
12901
+ image: MinVersionSchema.optional()
12902
+ }).optional()
12903
+ });
12904
+ /** One unmet requirement; each names the versions on both sides. */
12905
+ var UnmetRequirementSchema = discriminatedUnion("kind", [
12906
+ object({
12907
+ kind: literal("server-too-old"),
12908
+ required: string(),
12909
+ /** `null` — the running server version could not be read. */
12910
+ running: string().nullable()
12911
+ }),
12912
+ object({
12913
+ kind: literal("addon-too-old"),
12914
+ packageName: string(),
12915
+ required: string(),
12916
+ /** `null` — the addon is not installed (or its version is unreadable). */
12917
+ running: string().nullable()
12918
+ }),
12919
+ object({
12920
+ kind: literal("wrapper-too-old"),
12921
+ wrapper: _enum(["desktop", "image"]),
12922
+ required: string(),
12923
+ /** `null` — the wrapper reported no version. */
12924
+ running: string().nullable()
12925
+ })
12926
+ ]);
12927
+ var RequiredAddonUpdateSchema = object({
12928
+ packageName: string(),
12929
+ /** The minimum the target server declares. */
12930
+ required: string(),
12931
+ /** What this node has; `null` when the addon is not installed. */
12932
+ installed: string().nullable()
12933
+ });
12934
+ /**
12935
+ * Which integration instance a settings form is for. An addon that serves several instances
12936
+ * (`instanceMode: multiple`) can show a different form per instance — an NVR integration
12937
+ * needs its connection, the default integration needs the addon's own sections.
12938
+ *
12939
+ * - `integrationId` is `null` in the create wizard: nothing exists yet, the operator is
12940
+ * setting up a NEW instance.
12941
+ * - `mode` is the wizard mode of the integration type (`getAvailableTypes`).
12942
+ * - `candidateStableId` is the discovery candidate a wizard was seeded from, if any.
12943
+ */
12944
+ var IntegrationInstanceRefSchema = object({
12945
+ integrationId: string().nullable(),
12946
+ mode: _enum([
12947
+ "standalone",
12948
+ "account",
12949
+ "broker"
12950
+ ]),
12951
+ candidateStableId: string().optional()
12952
+ });
12953
+ /**
12719
12954
  * Build an `IAddonRouteProvider` from a list of routes. Implements
12720
12955
  * both the operator-facing `getRoutes` (returning route descriptors
12721
12956
  * minus the handlers, which can't cross JSON) and the framework-
@@ -13315,6 +13550,613 @@ var CameraSwitchGroupSchema = object({
13315
13550
  fetchedAt: number()
13316
13551
  });
13317
13552
  /**
13553
+ * Query filter for settings-store collections.
13554
+ */
13555
+ var QueryFilterSchema = object({
13556
+ where: record(string(), unknown()).optional(),
13557
+ whereIn: record(string(), array(unknown())).optional(),
13558
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13559
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
13560
+ whereNot: record(string(), unknown()).optional(),
13561
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
13562
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
13563
+ whereNotIn: record(string(), array(unknown())).optional(),
13564
+ orderBy: object({
13565
+ field: string(),
13566
+ direction: _enum(["asc", "desc"])
13567
+ }).optional(),
13568
+ limit: number().optional(),
13569
+ offset: number().optional()
13570
+ });
13571
+ /**
13572
+ * The predicate half of a filter, for BULK MUTATIONS.
13573
+ *
13574
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
13575
+ * meaning for a statement that rewrites a set, and accepting them would invite
13576
+ * a caller to believe `limit` bounds the damage. Every field is optional here
13577
+ * only so the shape stays composable — the implementation REJECTS a filter
13578
+ * that compiles to no predicate, because that is the whole collection.
13579
+ */
13580
+ var MutationFilterSchema = object({
13581
+ where: record(string(), unknown()).optional(),
13582
+ whereIn: record(string(), array(unknown())).optional(),
13583
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13584
+ whereNot: record(string(), unknown()).optional(),
13585
+ whereNotIn: record(string(), array(unknown())).optional()
13586
+ });
13587
+ /**
13588
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
13589
+ *
13590
+ * `as` names the slot in the result, so the SAME column may be asked twice with
13591
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
13592
+ * a `Record<column, op>` shape could not express.
13593
+ */
13594
+ var AggregateFieldSchema = object({
13595
+ /** Result key. */
13596
+ as: string().min(1),
13597
+ /** Column to aggregate. Must be a real column of a declared collection. */
13598
+ field: string().min(1),
13599
+ op: _enum([
13600
+ "sum",
13601
+ "min",
13602
+ "max"
13603
+ ])
13604
+ });
13605
+ /**
13606
+ * `COUNT(*)` plus one number per requested field.
13607
+ *
13608
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
13609
+ * that really is 0 are different facts, and an accounting caller that renders
13610
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
13611
+ */
13612
+ var AggregateResultSchema = object({
13613
+ count: number().int(),
13614
+ values: record(string(), number().nullable())
13615
+ });
13616
+ /** A single stored record: `{ id, data }`. */
13617
+ var SettingsRecordSchema = object({
13618
+ id: string(),
13619
+ data: record(string(), unknown())
13620
+ });
13621
+ /**
13622
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
13623
+ * optional.
13624
+ *
13625
+ * A separate schema rather than loosening the shared one: every other method
13626
+ * on this cap addresses a row BY its id, and making that field optional
13627
+ * everywhere would turn a forgotten key into a silently generated one on
13628
+ * `update` and `delete` as well.
13629
+ */
13630
+ var BulkRecordSchema = object({
13631
+ id: string().optional(),
13632
+ data: record(string(), unknown())
13633
+ });
13634
+ /**
13635
+ * The database FILES one `sqlite-settings` engine process owns (D851).
13636
+ * Closed on purpose: a file is a thing the engine opens, backs up and gives a
13637
+ * serving thread, so a new one is a code change, never a string a caller
13638
+ * invents. `settings` is `camstack.db` and the default for every collection
13639
+ * that names no file.
13640
+ */
13641
+ var DataStoreDatabaseSchema = _enum([
13642
+ "settings",
13643
+ "events",
13644
+ "history"
13645
+ ]);
13646
+ DataStoreDatabaseSchema.options;
13647
+ /**
13648
+ * One write inside a {@link settingsStoreCapability.methods.transact} call.
13649
+ * The three that a "replace this set, bump that revision" write needs, plus
13650
+ * the batched upsert a rollup writer needs.
13651
+ */
13652
+ var TransactOpSchema = discriminatedUnion("op", [
13653
+ object({
13654
+ op: literal("deleteWhere"),
13655
+ collection: string(),
13656
+ filter: MutationFilterSchema
13657
+ }),
13658
+ object({
13659
+ op: literal("insertMany"),
13660
+ collection: string(),
13661
+ records: array(BulkRecordSchema).readonly()
13662
+ }),
13663
+ object({
13664
+ op: literal("update"),
13665
+ collection: string(),
13666
+ id: string(),
13667
+ data: record(string(), unknown())
13668
+ }),
13669
+ object({
13670
+ op: literal("upsertMany"),
13671
+ collection: string(),
13672
+ conflict: array(string()).readonly(),
13673
+ records: array(BulkRecordSchema).readonly()
13674
+ })
13675
+ ]);
13676
+ /**
13677
+ * The precondition of a transaction: the row `key` of `collection` must hold
13678
+ * `equals` in `column`, read INSIDE the transaction.
13679
+ */
13680
+ var TransactGuardSchema = object({
13681
+ collection: string(),
13682
+ key: string(),
13683
+ column: string(),
13684
+ equals: unknown()
13685
+ });
13686
+ /**
13687
+ * Column declaration for a structured (SQL-backed) collection.
13688
+ *
13689
+ * Logical types — the backend translates each to the matching SQLite
13690
+ * storage class and handles per-type marshaling:
13691
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
13692
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
13693
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
13694
+ */
13695
+ var CollectionColumnSchema = object({
13696
+ name: string(),
13697
+ type: _enum([
13698
+ "TEXT",
13699
+ "INTEGER",
13700
+ "REAL",
13701
+ "JSON",
13702
+ "BOOLEAN"
13703
+ ]),
13704
+ primaryKey: boolean().optional(),
13705
+ notNull: boolean().optional(),
13706
+ unique: boolean().optional(),
13707
+ /**
13708
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
13709
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
13710
+ * collection surface simply cannot express their existing tables — which is
13711
+ * why they were still on the uncapped `table*` API.
13712
+ */
13713
+ defaultValue: union([
13714
+ string(),
13715
+ number(),
13716
+ boolean()
13717
+ ]).optional()
13718
+ });
13719
+ var CollectionIndexSchema = object({
13720
+ name: string(),
13721
+ columns: array(string()).readonly(),
13722
+ unique: boolean().optional()
13723
+ });
13724
+ /**
13725
+ * The input of `declareCollection` — ONE schema for the door and for the
13726
+ * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
13727
+ * the payload untouched).
13728
+ *
13729
+ * - `database` — the file the collection lives in (D851). Absent means
13730
+ * `settings`. A declaration naming a file other than the one the table is
13731
+ * already in is REFUSED by the engine, never moved.
13732
+ * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
13733
+ * as `primaryKey: true` on that column. A composite-keyed collection has no
13734
+ * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
13735
+ * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
13736
+ * address one row by id are refused.
13737
+ * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
13738
+ */
13739
+ var DeclareCollectionInputSchema = object({
13740
+ namespace: string().optional(),
13741
+ collection: string(),
13742
+ columns: array(CollectionColumnSchema).readonly(),
13743
+ indexes: array(CollectionIndexSchema).readonly().optional(),
13744
+ database: DataStoreDatabaseSchema.optional(),
13745
+ primaryKey: array(string()).readonly().optional(),
13746
+ withoutRowid: boolean().optional()
13747
+ });
13748
+ method(object({
13749
+ namespace: string().optional(),
13750
+ collection: string(),
13751
+ key: string()
13752
+ }), unknown(), { auth: "admin" }), method(object({
13753
+ namespace: string().optional(),
13754
+ collection: string(),
13755
+ key: string(),
13756
+ value: unknown()
13757
+ }), _void(), {
13758
+ kind: "mutation",
13759
+ auth: "admin"
13760
+ }), method(object({
13761
+ namespace: string().optional(),
13762
+ collection: string(),
13763
+ filter: QueryFilterSchema.optional(),
13764
+ columns: array(string()).readonly().optional()
13765
+ }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
13766
+ namespace: string().optional(),
13767
+ collection: string(),
13768
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
13769
+ * index on the collection. Never empty. */
13770
+ conflict: array(string()).readonly(),
13771
+ record: BulkRecordSchema
13772
+ }), object({
13773
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
13774
+ * row's own id on an update. */
13775
+ id: union([string(), number()]) }), {
13776
+ kind: "mutation",
13777
+ auth: "admin"
13778
+ }), method(object({
13779
+ namespace: string().optional(),
13780
+ collection: string(),
13781
+ conflict: array(string()).readonly(),
13782
+ records: array(BulkRecordSchema).readonly()
13783
+ }), object({ upserted: number().int() }), {
13784
+ kind: "mutation",
13785
+ auth: "admin"
13786
+ }), method(object({
13787
+ namespace: string().optional(),
13788
+ collection: string(),
13789
+ record: object({
13790
+ id: string().optional(),
13791
+ data: record(string(), unknown())
13792
+ })
13793
+ }), object({
13794
+ /**
13795
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
13796
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
13797
+ * primary key — which is the only place an auto key is knowable.
13798
+ */
13799
+ id: union([string(), number()]) }), {
13800
+ kind: "mutation",
13801
+ auth: "admin"
13802
+ }), method(object({
13803
+ namespace: string().optional(),
13804
+ collection: string(),
13805
+ records: array(BulkRecordSchema).readonly()
13806
+ }), object({ inserted: number().int() }), {
13807
+ kind: "mutation",
13808
+ auth: "admin"
13809
+ }), method(object({
13810
+ namespace: string().optional(),
13811
+ guard: TransactGuardSchema.optional(),
13812
+ ops: array(TransactOpSchema).readonly()
13813
+ }), object({
13814
+ applied: boolean(),
13815
+ current: unknown().optional()
13816
+ }), {
13817
+ kind: "mutation",
13818
+ auth: "admin"
13819
+ }), method(object({
13820
+ namespace: string().optional(),
13821
+ collection: string(),
13822
+ id: string(),
13823
+ data: record(string(), unknown())
13824
+ }), _void(), {
13825
+ kind: "mutation",
13826
+ auth: "admin"
13827
+ }), method(object({
13828
+ namespace: string().optional(),
13829
+ collection: string(),
13830
+ key: string()
13831
+ }), _void(), {
13832
+ kind: "mutation",
13833
+ auth: "admin"
13834
+ }), method(object({
13835
+ namespace: string().optional(),
13836
+ collection: string(),
13837
+ filter: MutationFilterSchema
13838
+ }), object({ deleted: number().int() }), {
13839
+ kind: "mutation",
13840
+ auth: "admin"
13841
+ }), method(object({
13842
+ namespace: string().optional(),
13843
+ collection: string(),
13844
+ filter: MutationFilterSchema,
13845
+ data: record(string(), unknown())
13846
+ }), object({ updated: number().int() }), {
13847
+ kind: "mutation",
13848
+ auth: "admin"
13849
+ }), method(object({
13850
+ namespace: string().optional(),
13851
+ collection: string(),
13852
+ filter: QueryFilterSchema.optional()
13853
+ }), number(), { auth: "admin" }), method(object({
13854
+ namespace: string().optional(),
13855
+ collection: string(),
13856
+ fields: array(AggregateFieldSchema).readonly(),
13857
+ filter: QueryFilterSchema.optional()
13858
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
13859
+ namespace: string().optional(),
13860
+ collection: string(),
13861
+ field: string(),
13862
+ bucketSize: number().int().positive(),
13863
+ origin: number().int(),
13864
+ filter: QueryFilterSchema.optional()
13865
+ }), array(object({
13866
+ bucket: number().int(),
13867
+ count: number().int()
13868
+ })).readonly(), { auth: "admin" }), method(object({
13869
+ namespace: string().optional(),
13870
+ collection: string()
13871
+ }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
13872
+ kind: "mutation",
13873
+ auth: "admin"
13874
+ });
13875
+ var MigrationIdSchema = string().regex(/^[a-z0-9-]+\/\d{4}-\d{2}-\d{2}-[a-z0-9-]+$/, "a migration id is \"<owner>/<yyyy-mm-dd>-<slug>\"");
13876
+ var MigrationLevelSchema = _enum([
13877
+ "hub-store",
13878
+ "addon-state",
13879
+ "node-config",
13880
+ "viewer",
13881
+ "addon-internal"
13882
+ ]);
13883
+ var MigrationStoreSchema = _enum([
13884
+ "settings",
13885
+ "events",
13886
+ "history",
13887
+ "node-local",
13888
+ "addon-files"
13889
+ ]);
13890
+ var MigrationScopeSchema = _enum(["cluster", "per-node"]);
13891
+ /** What depends on a migration while it is pending: `block-owner` is the default. */
13892
+ var MigrationOnFailureSchema = _enum(["block-owner", "tolerate"]);
13893
+ /**
13894
+ * `sql` is a hub-only body over the owner's connection. `plan` returns edits the
13895
+ * engine applies inside the migration's own transaction (hub-owned: computed in
13896
+ * that transaction; addon-owned: computed by the owner and shipped to the engine).
13897
+ * `procedure` is addon-owned work that is not SQL (keychain, integration registry,
13898
+ * device tombstones); the ledger records its outcome and it must be idempotent.
13899
+ */
13900
+ var MigrationBodyKindSchema = _enum([
13901
+ "sql",
13902
+ "plan",
13903
+ "procedure"
13904
+ ]);
13905
+ var MigrationMetaSchema = object({
13906
+ id: MigrationIdSchema,
13907
+ /** `hub`, an addon id, `node` or `viewer`. */
13908
+ owner: string().regex(/^[a-z0-9-]+$/),
13909
+ level: MigrationLevelSchema,
13910
+ /** Strictly increasing per owner: the order within an owner. */
13911
+ seq: number().int().positive(),
13912
+ /** Ids of any owner: the order across owners. */
13913
+ dependsOn: array(MigrationIdSchema),
13914
+ store: MigrationStoreSchema,
13915
+ scope: MigrationScopeSchema,
13916
+ onFailure: MigrationOnFailureSchema,
13917
+ bodyKind: MigrationBodyKindSchema,
13918
+ description: string().min(1),
13919
+ /** The marked field the migration's spec must assert survived. */
13920
+ fixtureMark: string().min(1)
13921
+ }).refine((m) => m.id.startsWith(`${m.owner}/`), {
13922
+ message: "the id must start with \"<owner>/\"",
13923
+ path: ["id"]
13924
+ });
13925
+ /** A cycle in the dependency graph, named by the ids on it. */
13926
+ var MigrationCycleError = class extends Error {
13927
+ ids;
13928
+ constructor(ids) {
13929
+ super(`migration dependsOn cycle: ${ids.join(" -> ")}`);
13930
+ this.ids = ids;
13931
+ this.name = "MigrationCycleError";
13932
+ }
13933
+ };
13934
+ /**
13935
+ * Topological order over `dependsOn` plus the implicit edge "a lower `seq` of an
13936
+ * owner runs first". Ties are broken by `(owner, seq)`. Throws
13937
+ * {@link MigrationCycleError} on a cycle. A `dependsOn` id that is not in the list
13938
+ * is ignored here — the catalogue schema is what refuses it.
13939
+ */
13940
+ function orderMigrations(items) {
13941
+ const byId = new Map(items.map((item) => [item.id, item]));
13942
+ const deps = /* @__PURE__ */ new Map();
13943
+ const ownerSorted = /* @__PURE__ */ new Map();
13944
+ for (const item of items) {
13945
+ const list = ownerSorted.get(item.owner) ?? [];
13946
+ list.push(item);
13947
+ ownerSorted.set(item.owner, list);
13948
+ }
13949
+ for (const item of items) deps.set(item.id, new Set(item.dependsOn.filter((id) => byId.has(id))));
13950
+ for (const list of ownerSorted.values()) {
13951
+ list.sort((a, b) => a.seq - b.seq);
13952
+ for (let i = 1; i < list.length; i++) {
13953
+ const previous = list[i - 1];
13954
+ const current = list[i];
13955
+ if (previous !== void 0 && current !== void 0) deps.get(current.id)?.add(previous.id);
13956
+ }
13957
+ }
13958
+ const tie = (a, b) => a.owner < b.owner ? -1 : a.owner > b.owner ? 1 : a.seq - b.seq;
13959
+ const done = /* @__PURE__ */ new Set();
13960
+ const ordered = [];
13961
+ let remaining = [...items];
13962
+ while (remaining.length > 0) {
13963
+ const ready = remaining.filter((item) => [...deps.get(item.id) ?? []].every((dep) => done.has(dep)));
13964
+ if (ready.length === 0) throw new MigrationCycleError(remaining.map((item) => item.id).sort());
13965
+ ready.sort(tie);
13966
+ const next = ready[0];
13967
+ if (next === void 0) break;
13968
+ ordered.push(next);
13969
+ done.add(next.id);
13970
+ remaining = remaining.filter((item) => item.id !== next.id);
13971
+ }
13972
+ return ordered;
13973
+ }
13974
+ /** Every way a list of migrations (in declaration order) is not a catalogue. */
13975
+ function catalogueIssues(items) {
13976
+ const issues = [];
13977
+ const seen = /* @__PURE__ */ new Set();
13978
+ const known = new Set(items.map((item) => item.id));
13979
+ const lastSeq = /* @__PURE__ */ new Map();
13980
+ const seqUsed = /* @__PURE__ */ new Map();
13981
+ for (const item of items) {
13982
+ if (seen.has(item.id)) issues.push({
13983
+ code: "duplicate-id",
13984
+ ids: [item.id],
13985
+ message: `duplicate id ${item.id}`
13986
+ });
13987
+ seen.add(item.id);
13988
+ const used = seqUsed.get(item.owner) ?? /* @__PURE__ */ new Set();
13989
+ if (used.has(item.seq)) issues.push({
13990
+ code: "duplicate-seq",
13991
+ ids: [item.id],
13992
+ message: `owner ${item.owner} has two migrations with seq ${item.seq}`
13993
+ });
13994
+ else if (item.seq < (lastSeq.get(item.owner) ?? 0)) issues.push({
13995
+ code: "seq-order",
13996
+ ids: [item.id],
13997
+ message: `seq ${item.seq} of ${item.id} does not increase in declaration order (owner ${item.owner})`
13998
+ });
13999
+ used.add(item.seq);
14000
+ seqUsed.set(item.owner, used);
14001
+ lastSeq.set(item.owner, Math.max(item.seq, lastSeq.get(item.owner) ?? 0));
14002
+ for (const dep of item.dependsOn) if (!known.has(dep)) issues.push({
14003
+ code: "unknown-dependency",
14004
+ ids: [item.id, dep],
14005
+ message: `${item.id} dependsOn unknown id ${dep}`
14006
+ });
14007
+ }
14008
+ try {
14009
+ orderMigrations(items);
14010
+ } catch (err) {
14011
+ if (err instanceof MigrationCycleError) issues.push({
14012
+ code: "cycle",
14013
+ ids: err.ids,
14014
+ message: err.message
14015
+ });
14016
+ else throw err;
14017
+ }
14018
+ return issues;
14019
+ }
14020
+ array(MigrationMetaSchema).superRefine((items, ctx) => {
14021
+ for (const issue of catalogueIssues(items)) ctx.addIssue({
14022
+ code: "custom",
14023
+ message: `${issue.code}: ${issue.message}`
14024
+ });
14025
+ });
14026
+ /**
14027
+ * The canonical JSON (`preconditionOf`) of the value a plan read; the apply step
14028
+ * re-reads, and a plan whose read no longer matches is refused (the migration is
14029
+ * `failed`, nothing it edited is kept).
14030
+ */
14031
+ var PreconditionSchema = string().min(1).optional();
14032
+ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
14033
+ object({
14034
+ op: literal("setKeys"),
14035
+ scope: string().min(1),
14036
+ values: record(string(), unknown()),
14037
+ precondition: PreconditionSchema
14038
+ }),
14039
+ object({
14040
+ op: literal("deleteKeys"),
14041
+ scope: string().min(1),
14042
+ keys: array(string().min(1)).min(1),
14043
+ precondition: PreconditionSchema
14044
+ }),
14045
+ object({
14046
+ op: literal("putRow"),
14047
+ table: string().min(1),
14048
+ key: record(string(), unknown()),
14049
+ row: record(string(), unknown()),
14050
+ precondition: PreconditionSchema
14051
+ }),
14052
+ object({
14053
+ op: literal("deleteRow"),
14054
+ table: string().min(1),
14055
+ key: record(string(), unknown()),
14056
+ precondition: PreconditionSchema
14057
+ }),
14058
+ object({
14059
+ op: literal("upsertRecord"),
14060
+ kind: string().min(1),
14061
+ recordKey: string().min(1),
14062
+ value: unknown(),
14063
+ precondition: PreconditionSchema
14064
+ }),
14065
+ object({
14066
+ op: literal("deleteRecord"),
14067
+ kind: string().min(1),
14068
+ recordKey: string().min(1),
14069
+ precondition: PreconditionSchema
14070
+ })
14071
+ ])) });
14072
+ /**
14073
+ * What a `procedure` body reports. `already-done` means the rerun observed the
14074
+ * work finished (a crash between the work and the ledger row, or a manual fix)
14075
+ * and did none of it again; the ledger records `applied` either way.
14076
+ */
14077
+ var MigrationProcedureResultSchema = object({
14078
+ outcome: _enum(["done", "already-done"]),
14079
+ summary: record(string(), unknown())
14080
+ });
14081
+ /** What an owner hands the engine when one of its migrations ran. */
14082
+ var MigrationOwnerResultSchema = discriminatedUnion("kind", [
14083
+ object({
14084
+ kind: literal("plan"),
14085
+ plan: MigrationPlanSchema
14086
+ }),
14087
+ object({
14088
+ kind: literal("procedure"),
14089
+ result: MigrationProcedureResultSchema
14090
+ }),
14091
+ object({
14092
+ kind: literal("failed"),
14093
+ error: string().min(1)
14094
+ })
14095
+ ]);
14096
+ /**
14097
+ * `held`: a restore of a pre-migration snapshot undid this id; it does not rerun
14098
+ * until the operator retries or a new code version ships.
14099
+ * `skipped-newer`: the database knows an id this code does not.
14100
+ */
14101
+ var MigrationStatusSchema = _enum([
14102
+ "applied",
14103
+ "failed",
14104
+ "blocked",
14105
+ "running",
14106
+ "held",
14107
+ "skipped-newer"
14108
+ ]);
14109
+ var MigrationLedgerEntrySchema = object({
14110
+ id: string(),
14111
+ database: DataStoreDatabaseSchema,
14112
+ owner: string(),
14113
+ level: MigrationLevelSchema,
14114
+ seq: number().int().nonnegative(),
14115
+ status: MigrationStatusSchema,
14116
+ attempts: number().int().nonnegative(),
14117
+ appliedAt: number().nullable(),
14118
+ codeVersion: string().nullable(),
14119
+ /** What the apply changed: rows per table, as the engine counted them. */
14120
+ planSummary: record(string(), unknown()).nullable(),
14121
+ progress: record(string(), unknown()).nullable(),
14122
+ error: string().nullable(),
14123
+ /** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
14124
+ reason: string().nullable(),
14125
+ nodeId: string().nullable(),
14126
+ bootId: string().nullable()
14127
+ });
14128
+ /** A catalogued migration that is not applied, with where it stands. */
14129
+ var MigrationPendingEntrySchema = object({
14130
+ meta: MigrationMetaSchema,
14131
+ database: DataStoreDatabaseSchema,
14132
+ state: _enum([
14133
+ "pending",
14134
+ "failed",
14135
+ "blocked",
14136
+ "held"
14137
+ ]),
14138
+ reason: string().nullable()
14139
+ });
14140
+ /** Rows per table before and after a dry run, and what it would have changed. */
14141
+ var MigrationDryRunResultSchema = object({
14142
+ id: string(),
14143
+ ok: boolean(),
14144
+ error: string().nullable(),
14145
+ rowsChanged: number().int().nonnegative(),
14146
+ tables: array(object({
14147
+ table: string(),
14148
+ before: number().int().nonnegative(),
14149
+ after: number().int().nonnegative()
14150
+ }))
14151
+ });
14152
+ /** One of an owner's migrations, as the engine tells the owner to treat it this boot. */
14153
+ var MigrationOwnerStepSchema = object({
14154
+ id: MigrationIdSchema,
14155
+ action: _enum(["run", "skip"]),
14156
+ /** The ledger state the engine found: `pending` has no row yet. */
14157
+ state: union([literal("pending"), MigrationStatusSchema])
14158
+ });
14159
+ /**
13318
14160
  * The trainer contract — what the hub hands the cloud trainer and what it
13319
14161
  * gets back (cloud training, D805+).
13320
14162
  *
@@ -16717,6 +17559,41 @@ var ACTUATOR_CAP_VOCABULARY = {
16717
17559
  icon: "droplets"
16718
17560
  }]
16719
17561
  },
17562
+ "power-source": {
17563
+ scope: "device",
17564
+ noun: t("Power source", "Alimentazione"),
17565
+ actions: [
17566
+ {
17567
+ id: "powerSource.on",
17568
+ method: "powerOn",
17569
+ label: t("Power on", "Dai corrente"),
17570
+ phrase: t("Power on {device}", "Dai corrente a {device}"),
17571
+ icon: "power"
17572
+ },
17573
+ {
17574
+ id: "powerSource.off",
17575
+ method: "powerOff",
17576
+ label: t("Power off", "Togli corrente"),
17577
+ phrase: t("Cut the power of {device}", "Togli la corrente a {device}"),
17578
+ icon: "power-off"
17579
+ },
17580
+ {
17581
+ id: "powerSource.forceRestart",
17582
+ method: "forceRestart",
17583
+ label: t("Force restart", "Riavvio forzato"),
17584
+ phrase: t("Force restart {device} by cutting its power", "Riavvia {device} togliendo la corrente"),
17585
+ icon: "refresh-cw"
17586
+ }
17587
+ ],
17588
+ state: [{
17589
+ kind: "boolean",
17590
+ field: "switchOn",
17591
+ label: t("Power switch on", "Presa accesa"),
17592
+ whenTrue: t("When the power of {device} comes on", "Quando arriva la corrente a {device}"),
17593
+ whenFalse: t("When the power of {device} is cut", "Quando manca la corrente a {device}"),
17594
+ icon: "power"
17595
+ }]
17596
+ },
16720
17597
  "lock-control": {
16721
17598
  scope: "device",
16722
17599
  noun: t("Lock", "Serratura"),
@@ -17192,7 +18069,7 @@ var ACTUATOR_CAP_VOCABULARY = {
17192
18069
  method: "setDirection",
17193
18070
  label: t("Spin forward", "Rotazione avanti"),
17194
18071
  phrase: t("Spin {device} forward", "Fai girare {device} in avanti"),
17195
- icon: "rotate-cw",
18072
+ icon: "refresh-cw",
17196
18073
  effect: {
17197
18074
  field: "direction",
17198
18075
  value: "forward"
@@ -20270,6 +21147,7 @@ var SYSTEM_CAP_VOCABULARY = {
20270
21147
  "login-method": system(IDENTITY),
20271
21148
  "mesh-network": system(NETWORK),
20272
21149
  "metrics-provider": system(CLUSTER),
21150
+ migrations: system(INTERNAL),
20273
21151
  "model-convert": system(ML),
20274
21152
  "model-distributor": system(ML),
20275
21153
  "mqtt-broker": system(NETWORK),
@@ -40472,7 +41350,8 @@ function normalizeUnit(raw) {
40472
41350
  if (lower === "dbm") return "dBm";
40473
41351
  if (lower === "dbfs") return "dBFS";
40474
41352
  if (lower === "inhg") return "inHg";
40475
- if (lower === "mm/h") return "mm/h";
41353
+ if (lower === "mm/h" || lower === "mm/hr") return "mm/h";
41354
+ if (lower === "in/h" || lower === "in/hr") return "in/h";
40476
41355
  if (lower === "w/m²" || lower === "w/m2") return "W/m²";
40477
41356
  if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
40478
41357
  if (lower === "kbps") return "kbps";
@@ -42866,7 +43745,7 @@ function pruneDeletedEntities(selection, live, log, exporter) {
42866
43745
  * rebuilds without manual reload.
42867
43746
  *
42868
43747
  * The hub-local builtin `addon-pages-aggregator` (see
42869
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
43748
+ * `@camstack/server/system/builtins/addon-pages-aggregator`) registers the
42870
43749
  * provider. Splitting the public aggregator from the raw collection
42871
43750
  * keeps both ends in codegen — there's no hand-written
42872
43751
  * `addon-pages.router.ts` wrapper anymore.
@@ -42956,7 +43835,7 @@ var addonRoutesCapability = {
42956
43835
  */
42957
43836
  invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
42958
43837
  },
42959
- /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
43838
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/server/system` `buildCapRouters`. */
42960
43839
  mount: { kind: "skip" }
42961
43840
  };
42962
43841
  var ConfigTabDeclarationSchema = object({
@@ -43011,6 +43890,10 @@ method(object({
43011
43890
  }), method(object({
43012
43891
  addonId: string(),
43013
43892
  nodeId: string().optional()
43893
+ }), SettingsSchemaWithValuesSchema.nullable()), method(object({
43894
+ addonId: string(),
43895
+ nodeId: string().optional(),
43896
+ instance: IntegrationInstanceRefSchema
43014
43897
  }), SettingsSchemaWithValuesSchema.nullable()), method(object({
43015
43898
  addonId: string(),
43016
43899
  nodeId: string().optional()
@@ -43181,7 +44064,7 @@ method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "admin" });
43181
44064
  * manual reload — same scheme used by `addon-pages`.
43182
44065
  *
43183
44066
  * The hub-local builtin `addon-widgets-aggregator` (see
43184
- * `@camstack/system/builtins/addon-widgets-aggregator`) registers the
44067
+ * `@camstack/server/system/builtins/addon-widgets-aggregator`) registers the
43185
44068
  * provider. Splitting the public aggregator from the raw collection
43186
44069
  * keeps both ends in codegen — there's no hand-written wrapper.
43187
44070
  */
@@ -43542,329 +44425,6 @@ method(object({
43542
44425
  password: string()
43543
44426
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
43544
44427
  /**
43545
- * Query filter for settings-store collections.
43546
- */
43547
- var QueryFilterSchema = object({
43548
- where: record(string(), unknown()).optional(),
43549
- whereIn: record(string(), array(unknown())).optional(),
43550
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
43551
- /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
43552
- whereNot: record(string(), unknown()).optional(),
43553
- /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
43554
- * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
43555
- whereNotIn: record(string(), array(unknown())).optional(),
43556
- orderBy: object({
43557
- field: string(),
43558
- direction: _enum(["asc", "desc"])
43559
- }).optional(),
43560
- limit: number().optional(),
43561
- offset: number().optional()
43562
- });
43563
- /**
43564
- * The predicate half of a filter, for BULK MUTATIONS.
43565
- *
43566
- * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
43567
- * meaning for a statement that rewrites a set, and accepting them would invite
43568
- * a caller to believe `limit` bounds the damage. Every field is optional here
43569
- * only so the shape stays composable — the implementation REJECTS a filter
43570
- * that compiles to no predicate, because that is the whole collection.
43571
- */
43572
- var MutationFilterSchema = object({
43573
- where: record(string(), unknown()).optional(),
43574
- whereIn: record(string(), array(unknown())).optional(),
43575
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
43576
- whereNot: record(string(), unknown()).optional(),
43577
- whereNotIn: record(string(), array(unknown())).optional()
43578
- });
43579
- /**
43580
- * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
43581
- *
43582
- * `as` names the slot in the result, so the SAME column may be asked twice with
43583
- * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
43584
- * a `Record<column, op>` shape could not express.
43585
- */
43586
- var AggregateFieldSchema = object({
43587
- /** Result key. */
43588
- as: string().min(1),
43589
- /** Column to aggregate. Must be a real column of a declared collection. */
43590
- field: string().min(1),
43591
- op: _enum([
43592
- "sum",
43593
- "min",
43594
- "max"
43595
- ])
43596
- });
43597
- /**
43598
- * `COUNT(*)` plus one number per requested field.
43599
- *
43600
- * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
43601
- * that really is 0 are different facts, and an accounting caller that renders
43602
- * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
43603
- */
43604
- var AggregateResultSchema = object({
43605
- count: number().int(),
43606
- values: record(string(), number().nullable())
43607
- });
43608
- /** A single stored record: `{ id, data }`. */
43609
- var SettingsRecordSchema = object({
43610
- id: string(),
43611
- data: record(string(), unknown())
43612
- });
43613
- /**
43614
- * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
43615
- * optional.
43616
- *
43617
- * A separate schema rather than loosening the shared one: every other method
43618
- * on this cap addresses a row BY its id, and making that field optional
43619
- * everywhere would turn a forgotten key into a silently generated one on
43620
- * `update` and `delete` as well.
43621
- */
43622
- var BulkRecordSchema = object({
43623
- id: string().optional(),
43624
- data: record(string(), unknown())
43625
- });
43626
- /**
43627
- * The database FILES one `sqlite-settings` engine process owns (D851).
43628
- * Closed on purpose: a file is a thing the engine opens, backs up and gives a
43629
- * serving thread, so a new one is a code change, never a string a caller
43630
- * invents. `settings` is `camstack.db` and the default for every collection
43631
- * that names no file.
43632
- */
43633
- var DataStoreDatabaseSchema = _enum([
43634
- "settings",
43635
- "events",
43636
- "history"
43637
- ]);
43638
- DataStoreDatabaseSchema.options;
43639
- /**
43640
- * One write inside a {@link settingsStoreCapability.methods.transact} call.
43641
- * The three that a "replace this set, bump that revision" write needs, plus
43642
- * the batched upsert a rollup writer needs.
43643
- */
43644
- var TransactOpSchema = discriminatedUnion("op", [
43645
- object({
43646
- op: literal("deleteWhere"),
43647
- collection: string(),
43648
- filter: MutationFilterSchema
43649
- }),
43650
- object({
43651
- op: literal("insertMany"),
43652
- collection: string(),
43653
- records: array(BulkRecordSchema).readonly()
43654
- }),
43655
- object({
43656
- op: literal("update"),
43657
- collection: string(),
43658
- id: string(),
43659
- data: record(string(), unknown())
43660
- }),
43661
- object({
43662
- op: literal("upsertMany"),
43663
- collection: string(),
43664
- conflict: array(string()).readonly(),
43665
- records: array(BulkRecordSchema).readonly()
43666
- })
43667
- ]);
43668
- /**
43669
- * The precondition of a transaction: the row `key` of `collection` must hold
43670
- * `equals` in `column`, read INSIDE the transaction.
43671
- */
43672
- var TransactGuardSchema = object({
43673
- collection: string(),
43674
- key: string(),
43675
- column: string(),
43676
- equals: unknown()
43677
- });
43678
- /**
43679
- * Column declaration for a structured (SQL-backed) collection.
43680
- *
43681
- * Logical types — the backend translates each to the matching SQLite
43682
- * storage class and handles per-type marshaling:
43683
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
43684
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
43685
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
43686
- */
43687
- var CollectionColumnSchema = object({
43688
- name: string(),
43689
- type: _enum([
43690
- "TEXT",
43691
- "INTEGER",
43692
- "REAL",
43693
- "JSON",
43694
- "BOOLEAN"
43695
- ]),
43696
- primaryKey: boolean().optional(),
43697
- notNull: boolean().optional(),
43698
- unique: boolean().optional(),
43699
- /**
43700
- * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
43701
- * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
43702
- * collection surface simply cannot express their existing tables — which is
43703
- * why they were still on the uncapped `table*` API.
43704
- */
43705
- defaultValue: union([
43706
- string(),
43707
- number(),
43708
- boolean()
43709
- ]).optional()
43710
- });
43711
- var CollectionIndexSchema = object({
43712
- name: string(),
43713
- columns: array(string()).readonly(),
43714
- unique: boolean().optional()
43715
- });
43716
- /**
43717
- * The input of `declareCollection` — ONE schema for the door and for the
43718
- * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
43719
- * the payload untouched).
43720
- *
43721
- * - `database` — the file the collection lives in (D851). Absent means
43722
- * `settings`. A declaration naming a file other than the one the table is
43723
- * already in is REFUSED by the engine, never moved.
43724
- * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
43725
- * as `primaryKey: true` on that column. A composite-keyed collection has no
43726
- * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
43727
- * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
43728
- * address one row by id are refused.
43729
- * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
43730
- */
43731
- var DeclareCollectionInputSchema = object({
43732
- namespace: string().optional(),
43733
- collection: string(),
43734
- columns: array(CollectionColumnSchema).readonly(),
43735
- indexes: array(CollectionIndexSchema).readonly().optional(),
43736
- database: DataStoreDatabaseSchema.optional(),
43737
- primaryKey: array(string()).readonly().optional(),
43738
- withoutRowid: boolean().optional()
43739
- });
43740
- method(object({
43741
- namespace: string().optional(),
43742
- collection: string(),
43743
- key: string()
43744
- }), unknown(), { auth: "admin" }), method(object({
43745
- namespace: string().optional(),
43746
- collection: string(),
43747
- key: string(),
43748
- value: unknown()
43749
- }), _void(), {
43750
- kind: "mutation",
43751
- auth: "admin"
43752
- }), method(object({
43753
- namespace: string().optional(),
43754
- collection: string(),
43755
- filter: QueryFilterSchema.optional(),
43756
- columns: array(string()).readonly().optional()
43757
- }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
43758
- namespace: string().optional(),
43759
- collection: string(),
43760
- /** Declared columns identifying the slot; must be covered by a UNIQUE
43761
- * index on the collection. Never empty. */
43762
- conflict: array(string()).readonly(),
43763
- record: BulkRecordSchema
43764
- }), object({
43765
- /** The id the row HAS: assigned by SQLite on an insert, or the existing
43766
- * row's own id on an update. */
43767
- id: union([string(), number()]) }), {
43768
- kind: "mutation",
43769
- auth: "admin"
43770
- }), method(object({
43771
- namespace: string().optional(),
43772
- collection: string(),
43773
- conflict: array(string()).readonly(),
43774
- records: array(BulkRecordSchema).readonly()
43775
- }), object({ upserted: number().int() }), {
43776
- kind: "mutation",
43777
- auth: "admin"
43778
- }), method(object({
43779
- namespace: string().optional(),
43780
- collection: string(),
43781
- record: object({
43782
- id: string().optional(),
43783
- data: record(string(), unknown())
43784
- })
43785
- }), object({
43786
- /**
43787
- * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
43788
- * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
43789
- * primary key — which is the only place an auto key is knowable.
43790
- */
43791
- id: union([string(), number()]) }), {
43792
- kind: "mutation",
43793
- auth: "admin"
43794
- }), method(object({
43795
- namespace: string().optional(),
43796
- collection: string(),
43797
- records: array(BulkRecordSchema).readonly()
43798
- }), object({ inserted: number().int() }), {
43799
- kind: "mutation",
43800
- auth: "admin"
43801
- }), method(object({
43802
- namespace: string().optional(),
43803
- guard: TransactGuardSchema.optional(),
43804
- ops: array(TransactOpSchema).readonly()
43805
- }), object({
43806
- applied: boolean(),
43807
- current: unknown().optional()
43808
- }), {
43809
- kind: "mutation",
43810
- auth: "admin"
43811
- }), method(object({
43812
- namespace: string().optional(),
43813
- collection: string(),
43814
- id: string(),
43815
- data: record(string(), unknown())
43816
- }), _void(), {
43817
- kind: "mutation",
43818
- auth: "admin"
43819
- }), method(object({
43820
- namespace: string().optional(),
43821
- collection: string(),
43822
- key: string()
43823
- }), _void(), {
43824
- kind: "mutation",
43825
- auth: "admin"
43826
- }), method(object({
43827
- namespace: string().optional(),
43828
- collection: string(),
43829
- filter: MutationFilterSchema
43830
- }), object({ deleted: number().int() }), {
43831
- kind: "mutation",
43832
- auth: "admin"
43833
- }), method(object({
43834
- namespace: string().optional(),
43835
- collection: string(),
43836
- filter: MutationFilterSchema,
43837
- data: record(string(), unknown())
43838
- }), object({ updated: number().int() }), {
43839
- kind: "mutation",
43840
- auth: "admin"
43841
- }), method(object({
43842
- namespace: string().optional(),
43843
- collection: string(),
43844
- filter: QueryFilterSchema.optional()
43845
- }), number(), { auth: "admin" }), method(object({
43846
- namespace: string().optional(),
43847
- collection: string(),
43848
- fields: array(AggregateFieldSchema).readonly(),
43849
- filter: QueryFilterSchema.optional()
43850
- }), AggregateResultSchema, { auth: "admin" }), method(object({
43851
- namespace: string().optional(),
43852
- collection: string(),
43853
- field: string(),
43854
- bucketSize: number().int().positive(),
43855
- origin: number().int(),
43856
- filter: QueryFilterSchema.optional()
43857
- }), array(object({
43858
- bucket: number().int(),
43859
- count: number().int()
43860
- })).readonly(), { auth: "admin" }), method(object({
43861
- namespace: string().optional(),
43862
- collection: string()
43863
- }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
43864
- kind: "mutation",
43865
- auth: "admin"
43866
- });
43867
- /**
43868
44428
  * How long one snapshot may take end to end: the pinned copy of the whole
43869
44429
  * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
43870
44430
  * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
@@ -45827,7 +46387,7 @@ method(object({
45827
46387
  kind: "mutation",
45828
46388
  auth: "admin"
45829
46389
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
45830
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
46390
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
45831
46391
  var NUMBER = {
45832
46392
  kind: "fixed",
45833
46393
  result: ["number"]
@@ -46838,6 +47398,57 @@ id: union([string(), number()]) }), {
46838
47398
  kind: "mutation",
46839
47399
  auth: "admin"
46840
47400
  });
47401
+ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
47402
+ id: string().min(1),
47403
+ error: string().min(1)
47404
+ }), MigrationLedgerEntrySchema, {
47405
+ kind: "mutation",
47406
+ auth: "admin"
47407
+ }), method(object({ id: string().min(1) }), object({ entries: array(MigrationLedgerEntrySchema) }), {
47408
+ kind: "mutation",
47409
+ auth: "admin"
47410
+ }), method(object({
47411
+ database: DataStoreDatabaseSchema,
47412
+ snapshotId: string().min(1)
47413
+ }), object({
47414
+ scheduled: boolean(),
47415
+ held: array(string()),
47416
+ restartRequired: boolean()
47417
+ }), {
47418
+ kind: "mutation",
47419
+ auth: "admin"
47420
+ }), method(object({
47421
+ owner: string().min(1),
47422
+ metas: array(MigrationMetaSchema)
47423
+ }), object({
47424
+ bootId: string(),
47425
+ snapshot: _enum([
47426
+ "none",
47427
+ "written",
47428
+ "refused"
47429
+ ]),
47430
+ steps: array(MigrationOwnerStepSchema)
47431
+ }), {
47432
+ kind: "mutation",
47433
+ auth: "admin"
47434
+ }), method(object({ id: string().min(1) }), object({
47435
+ proceed: boolean(),
47436
+ state: union([literal("pending"), MigrationStatusSchema]),
47437
+ reason: string().nullable()
47438
+ }), {
47439
+ kind: "mutation",
47440
+ auth: "admin"
47441
+ }), method(object({
47442
+ id: string().min(1),
47443
+ result: MigrationOwnerResultSchema
47444
+ }), object({
47445
+ status: MigrationStatusSchema,
47446
+ error: string().nullable(),
47447
+ rowsChanged: number().int().nonnegative()
47448
+ }), {
47449
+ kind: "mutation",
47450
+ auth: "admin"
47451
+ });
46841
47452
  /**
46842
47453
  * records - an addon's own facts and documents (D881): ledgers, audit rows,
46843
47454
  * config documents, mirrors. The third storage API next to `ctx.settings`
@@ -61781,7 +62392,18 @@ var ServerUpdateActionResultSchema = object({
61781
62392
  * store (D877), present when the pre-flight refused the update
61782
62393
  * (`reason: 'addon-would-refuse'`). Optional for version skew.
61783
62394
  */
61784
- blockers: array(UpdateBlockerSchema).optional()
62395
+ blockers: array(UpdateBlockerSchema).optional(),
62396
+ /**
62397
+ * The minimums the target server declares and this node does not meet
62398
+ * (D989), present when the update was refused with
62399
+ * `reason: 'requirements-unmet'`. Each names the versions on both sides.
62400
+ */
62401
+ unmet: array(UnmetRequirementSchema).optional(),
62402
+ /**
62403
+ * The addons this node has below the minimum the staged server declares —
62404
+ * the ones to update WITH the server. Present on an accepted update.
62405
+ */
62406
+ requiredAddonUpdates: array(RequiredAddonUpdateSchema).optional()
61785
62407
  });
61786
62408
  var LaunchOverridesSchema = object({
61787
62409
  /** The persisted heap ceiling in MB, or `null` when none is set. */
@@ -64397,7 +65019,9 @@ var PackageUpdateSchema = object({
64397
65019
  latestVersion: string(),
64398
65020
  category: _enum(["addon", "core"]),
64399
65021
  requiresRestart: boolean(),
64400
- isSystem: boolean()
65022
+ isSystem: boolean(),
65023
+ /** Held: what the newer version needs and this node does not meet (D989). */
65024
+ blockedBy: array(UnmetRequirementSchema).readonly().optional()
64401
65025
  });
64402
65026
  var PackageVersionInfoSchema = object({
64403
65027
  version: string(),
@@ -65853,19 +66477,19 @@ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status:
65853
66477
  reason: _enum(["unknown-template", "not-a-media-player"]),
65854
66478
  detail: string()
65855
66479
  })]);
65856
- var deviceInput = object({ deviceId: number().int().nonnegative() });
66480
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
65857
66481
  DeviceType.MediaPlayer;
65858
- method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
66482
+ method(deviceInput$1, RemoteConfigSchema, { auth: "admin" }), method(deviceInput$1.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
65859
66483
  kind: "mutation",
65860
66484
  auth: "admin"
65861
- }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
66485
+ }), method(deviceInput$1, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput$1.extend({
65862
66486
  role: RemoteRoleSchema,
65863
66487
  /** Sends in a row (volume steps). */
65864
66488
  repeat: number().int().min(1).max(50).default(1)
65865
66489
  }), RemoteSendResultSchema, {
65866
66490
  kind: "mutation",
65867
66491
  auth: "protected"
65868
- }), method(deviceInput.extend({
66492
+ }), method(deviceInput$1.extend({
65869
66493
  command: RemoteMediaCommandSchema,
65870
66494
  muted: boolean().optional(),
65871
66495
  source: string().optional(),
@@ -69731,7 +70355,7 @@ var lawnMowerControlCapability = {
69731
70355
  * clients — they reverse-connect to the hub. Exposing their interfaces
69732
70356
  * via the same surface would leak internal topology with no upside.
69733
70357
  *
69734
- * Implementation in `@camstack/system/builtins/local-network/`.
70358
+ * Implementation in `@camstack/server/system/builtins/local-network/`.
69735
70359
  */
69736
70360
  /** Coarse classification derived from the interface name + IP range. */
69737
70361
  var InterfaceKindEnum = _enum([
@@ -70228,6 +70852,117 @@ var lockControlCapability = {
70228
70852
  * whether persisting is worth a SQLite commit. */
70229
70853
  volatileStateFields: ["lastChangedAt"]
70230
70854
  };
70855
+ var PowerSourceRefusalReasonSchema = _enum([
70856
+ "no-power-source",
70857
+ "switch-missing",
70858
+ "switch-offline",
70859
+ "switch-unknown",
70860
+ "restart-in-progress",
70861
+ "switch-call-failed"
70862
+ ]);
70863
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
70864
+ accepted: literal(false),
70865
+ reason: PowerSourceRefusalReasonSchema,
70866
+ /** One line for the operator, naming the switch. */
70867
+ message: string()
70868
+ })]);
70869
+ var PowerSourceInfoSchema = object({
70870
+ /** False when the device has no power source linked; the rest is then empty. */
70871
+ linked: boolean(),
70872
+ switchDeviceId: number().nullable(),
70873
+ switchName: string().nullable(),
70874
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
70875
+ switchOn: boolean().nullable(),
70876
+ switchOnline: boolean().nullable(),
70877
+ restartInProgress: boolean(),
70878
+ offSeconds: number().int()
70879
+ });
70880
+ /** The composed slice: the switch state the link mirrors, plus the stored off time. */
70881
+ var PowerSourceStateSchema = object({
70882
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
70883
+ switchOn: boolean().nullable(),
70884
+ offSeconds: number().int(),
70885
+ lastFetchedAt: number()
70886
+ });
70887
+ var POWER_SOURCE_DEVICE_TYPES = [
70888
+ DeviceType.Gateway,
70889
+ DeviceType.Hub,
70890
+ DeviceType.Camera,
70891
+ DeviceType.Sensor,
70892
+ DeviceType.MediaPlayer,
70893
+ DeviceType.Vacuum,
70894
+ DeviceType.Generic,
70895
+ DeviceType.Washer,
70896
+ DeviceType.Dryer,
70897
+ DeviceType.Dishwasher,
70898
+ DeviceType.PetFountain,
70899
+ DeviceType.Fan,
70900
+ DeviceType.Humidifier,
70901
+ DeviceType.Climate,
70902
+ DeviceType.WaterHeater
70903
+ ];
70904
+ var powerSourceCustomize = {
70905
+ version: 1,
70906
+ targets: [{
70907
+ id: "switch",
70908
+ cardinality: "scalar",
70909
+ vocabulary: {
70910
+ cap: "power-source",
70911
+ stateField: "switchOn"
70912
+ },
70913
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
70914
+ apply: "add",
70915
+ path: "switchOn",
70916
+ sources: [{
70917
+ kind: "switch",
70918
+ transforms: [{ kind: "identity" }]
70919
+ }],
70920
+ companions: [{
70921
+ kind: "constant",
70922
+ path: "offSeconds",
70923
+ value: 10,
70924
+ when: "add"
70925
+ }, {
70926
+ kind: "clock",
70927
+ path: "lastFetchedAt",
70928
+ when: "always"
70929
+ }],
70930
+ sourceReport: { status: "assignment-status" }
70931
+ }]
70932
+ };
70933
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
70934
+ var powerSourceCapability = {
70935
+ name: "power-source",
70936
+ scope: "device",
70937
+ mode: "singleton",
70938
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
70939
+ methods: {
70940
+ getStatus: systemMethod(deviceInput, PowerSourceInfoSchema, { auth: "admin" }),
70941
+ powerOn: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70942
+ kind: "mutation",
70943
+ auth: "admin"
70944
+ }),
70945
+ powerOff: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70946
+ kind: "mutation",
70947
+ auth: "admin"
70948
+ }),
70949
+ forceRestart: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70950
+ kind: "mutation",
70951
+ auth: "admin"
70952
+ })
70953
+ },
70954
+ runtimeState: PowerSourceStateSchema,
70955
+ customize: powerSourceCustomize,
70956
+ /**
70957
+ * Runtime-state durability: **session** — mirrored from the linked switch on
70958
+ * its next report; a restored value would outlive the reading it names.
70959
+ *
70960
+ * See `RuntimeStateDurability`. Enforced by
70961
+ * `scripts/check-runtime-state-durability.ts`.
70962
+ */
70963
+ durability: "session",
70964
+ history: "none"
70965
+ };
70231
70966
  /**
70232
70967
  * A monotonic total between resets (D944). Energy uses `power-meter.energy`
70233
70968
  * so dashboards keep one series; every other instantaneous reading lands here.
@@ -71508,7 +72243,7 @@ onStatusChanged: { data: object({
71508
72243
  * jitter, and observed/peak bandwidth per device + per client.
71509
72244
  *
71510
72245
  * Implementation lives in the server's `NetworkQualityService` (thin
71511
- * wrapper over the shared `NetworkQualityTracker` from `@camstack/system`).
72246
+ * wrapper over the shared `NetworkQualityTracker` from `@camstack/server/system`).
71512
72247
  * The provider is registered from `trpc.router.ts` against the existing
71513
72248
  * service instance — no addon owns this state.
71514
72249
  *
@@ -73659,7 +74394,7 @@ var ptzAutotrackCapability = {
73659
74394
  * - **Native**: the camera's own firmware autotrack, registered per device by
73660
74395
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
73661
74396
  * model-dependent).
73662
- * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
74397
+ * - **Wrapper**: the emulated implementation (`@camstack/server/system`'s `autotrack` builtin),
73663
74398
  * which follows a subject with `ptz.move` pulses driven by the detection
73664
74399
  * pipeline. `defaultActive: false` — the operator binds it per camera in
73665
74400
  * the bindings UI.
@@ -79013,6 +79748,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
79013
79748
  petFeeder: petFeederCapability,
79014
79749
  petFountain: petFountainCapability,
79015
79750
  powerMeter: powerMeterCapability,
79751
+ powerSource: powerSourceCapability,
79016
79752
  presence: presenceCapability,
79017
79753
  pressureSensor: pressureSensorCapability,
79018
79754
  privacyMask: privacyMaskCapability,
@@ -80682,6 +81418,13 @@ Object.freeze({
80682
81418
  access: "view",
80683
81419
  role: "admin"
80684
81420
  },
81421
+ "addonSettings.getIntegrationFormSchema": {
81422
+ capName: "addon-settings",
81423
+ capScope: "system",
81424
+ addonId: null,
81425
+ access: "view",
81426
+ role: "admin"
81427
+ },
80685
81428
  "addonSettings.getIntegrationSettings": {
80686
81429
  capName: "addon-settings",
80687
81430
  capScope: "system",
@@ -84770,6 +85513,69 @@ Object.freeze({
84770
85513
  access: "view",
84771
85514
  role: "admin"
84772
85515
  },
85516
+ "migrations.declareOwner": {
85517
+ capName: "migrations",
85518
+ capScope: "system",
85519
+ addonId: null,
85520
+ access: "create",
85521
+ role: "admin"
85522
+ },
85523
+ "migrations.dryRun": {
85524
+ capName: "migrations",
85525
+ capScope: "system",
85526
+ addonId: null,
85527
+ access: "view",
85528
+ role: "admin"
85529
+ },
85530
+ "migrations.finishMigration": {
85531
+ capName: "migrations",
85532
+ capScope: "system",
85533
+ addonId: null,
85534
+ access: "create",
85535
+ role: "admin"
85536
+ },
85537
+ "migrations.listLedger": {
85538
+ capName: "migrations",
85539
+ capScope: "system",
85540
+ addonId: null,
85541
+ access: "view",
85542
+ role: "admin"
85543
+ },
85544
+ "migrations.listPending": {
85545
+ capName: "migrations",
85546
+ capScope: "system",
85547
+ addonId: null,
85548
+ access: "view",
85549
+ role: "admin"
85550
+ },
85551
+ "migrations.markFailed": {
85552
+ capName: "migrations",
85553
+ capScope: "system",
85554
+ addonId: null,
85555
+ access: "create",
85556
+ role: "admin"
85557
+ },
85558
+ "migrations.restorePreMigration": {
85559
+ capName: "migrations",
85560
+ capScope: "system",
85561
+ addonId: null,
85562
+ access: "create",
85563
+ role: "admin"
85564
+ },
85565
+ "migrations.retry": {
85566
+ capName: "migrations",
85567
+ capScope: "system",
85568
+ addonId: null,
85569
+ access: "create",
85570
+ role: "admin"
85571
+ },
85572
+ "migrations.startMigration": {
85573
+ capName: "migrations",
85574
+ capScope: "system",
85575
+ addonId: null,
85576
+ access: "create",
85577
+ role: "admin"
85578
+ },
84773
85579
  "modelConvert.convert": {
84774
85580
  capName: "model-convert",
84775
85581
  capScope: "system",
@@ -87402,6 +88208,34 @@ Object.freeze({
87402
88208
  access: "view",
87403
88209
  role: "viewer"
87404
88210
  },
88211
+ "powerSource.forceRestart": {
88212
+ capName: "power-source",
88213
+ capScope: "device",
88214
+ addonId: null,
88215
+ access: "create",
88216
+ role: "admin"
88217
+ },
88218
+ "powerSource.getStatus": {
88219
+ capName: "power-source",
88220
+ capScope: "device",
88221
+ addonId: null,
88222
+ access: "view",
88223
+ role: "admin"
88224
+ },
88225
+ "powerSource.powerOff": {
88226
+ capName: "power-source",
88227
+ capScope: "device",
88228
+ addonId: null,
88229
+ access: "create",
88230
+ role: "admin"
88231
+ },
88232
+ "powerSource.powerOn": {
88233
+ capName: "power-source",
88234
+ capScope: "device",
88235
+ addonId: null,
88236
+ access: "create",
88237
+ role: "admin"
88238
+ },
87405
88239
  "presence.getStatus": {
87406
88240
  capName: "presence",
87407
88241
  capScope: "device",
@@ -92561,6 +93395,26 @@ Object.freeze({
92561
93395
  form: "single",
92562
93396
  optional: false
92563
93397
  }],
93398
+ "powerSource.forceRestart": [{
93399
+ name: "deviceId",
93400
+ form: "single",
93401
+ optional: false
93402
+ }],
93403
+ "powerSource.getStatus": [{
93404
+ name: "deviceId",
93405
+ form: "single",
93406
+ optional: false
93407
+ }],
93408
+ "powerSource.powerOff": [{
93409
+ name: "deviceId",
93410
+ form: "single",
93411
+ optional: false
93412
+ }],
93413
+ "powerSource.powerOn": [{
93414
+ name: "deviceId",
93415
+ form: "single",
93416
+ optional: false
93417
+ }],
92564
93418
  "presence.getStatus": [{
92565
93419
  name: "deviceId",
92566
93420
  form: "single",
@@ -95207,4 +96061,4 @@ async function listHomeAssistantBrokers(query) {
95207
96061
  return (await query({})).filter((broker) => broker.kind === HA_BROKER_KIND);
95208
96062
  }
95209
96063
  //#endregion
95210
- export { humiditySensorCapability as $, array as $t, brokerCapability as A, temperatureSensorCapability as At, consumableKindOf as B, DeviceRole as Bt, alarmPanelCapability as C, runningFromVacuumState as Ct, bestLocationMatch as D, smokeCapability as Dt, batteryCapability as E, sendToExportedTarget as Et, carbonMonoxideCapability as F, valveCapability as Ft, deviceAdoptionCapability as G, baseUnitOf as Gt, controlCapability as H, errMsg as Ht, climateControlCapability as I, vibrationCapability as It, eventEmitterCapability as J, defineRecordKinds as Jt, deviceExportCapability as K, createEvent as Kt, colorCapability as L, waterHeaterCapability as Lt, buildDeviceAdminUrl as M, tryConvertUnit as Mt, buttonCapability as N, updateCapability as Nt, binaryCapability as O, switchCapability as Ot, calendarSourceCapability as P, vacuumControlCapability as Pt, humidifierCapability as Q, _enum as Qt, connectivityCapability as R, weatherCapability as Rt, airQualitySensorCapability as S, runningFromMediaPlayer as St, automationControlCapability as T, scriptRunnerCapability as Tt, coverCapability as U, BaseAddon as Ut, contactCapability as V, DeviceType as Vt, deviceAdminLinkCapability as W, RecordSecretUnavailableError as Wt, floodCapability as X, isReadingQuantity as Xt, fanControlCapability as Y, entryBody as Yt, gasCapability as Z, nodePin as Zt, PLUG_APPLIANCE_TYPES as _, publicEndpointReaderFrom as _t, BaseDevice as a, record as an, motionCapability as at, accessoriesCapability as b, runExportedAction as bt, COCO_TO_MACRO as c, unknown as cn, notifierCapability as ct, ConsumableItemSchema as d, parseAdminBase as dt, boolean as en, imageCapability as et, ConsumableStatusSchema as f, powerMeterCapability as ft, MediaPlayerRepeatSchema as g, pruneDeletedEntities as gt, HvacModeSchema as h, pressureSensorCapability as ht, AlarmArmModeSchema as i, object as in, mediaPlayerCapability as it, buildAddonRouteProvider as j, tryConvertDelta as jt, brightnessCapability as k, tamperCapability as kt, CameraSwitchIdSchema as l, url as ln, numericSensorCapability as lt, FanDirectionSchema as m, presenceCapability as mt, HA_PROVIDER_ADDON_ID as n, literal as nn, lawnMowerControlCapability as nt, BaseDeviceProvider as o, string as on, normalizeUnit as ot, EnumSensorDateTimeFormatSchema as p, prepareNotification as pt, enumSensorCapability as q, defineEntryKind as qt, listHomeAssistantBrokers as r, number as rn, lockControlCapability as rt, CAMERA_SWITCH_ORDER as s, union as sn, notificationOutputCapability as st, HA_BROKER_KIND as t, discriminatedUnion as tn, ingestReading as tt, ConsumableFormSchema as u, EventCategory as un, oauthIntegrationCapability as ut, RUNNING_STATE_COLD as v, quantityFromUnit as vt, ambientLightSensorCapability as w, runningStateCapability as wt, addonRoutesCapability as x, runningFromLawnMower as xt, TargetSchema as y, resolveAttachmentUrls as yt, consumableFormOf as z, DeviceFeature as zt };
96064
+ export { humiditySensorCapability as $, number as $t, brokerCapability as A, temperatureSensorCapability as At, consumableKindOf as B, RecordSecretUnavailableError as Bt, alarmPanelCapability as C, runningFromVacuumState as Ct, bestLocationMatch as D, smokeCapability as Dt, batteryCapability as E, sendToExportedTarget as Et, carbonMonoxideCapability as F, valveCapability as Ft, deviceAdoptionCapability as G, entryBody as Gt, controlCapability as H, createEvent as Ht, climateControlCapability as I, vibrationCapability as It, eventEmitterCapability as J, _enum as Jt, deviceExportCapability as K, isReadingQuantity as Kt, colorCapability as L, waterHeaterCapability as Lt, buildDeviceAdminUrl as M, tryConvertUnit as Mt, buttonCapability as N, updateCapability as Nt, binaryCapability as O, switchCapability as Ot, calendarSourceCapability as P, vacuumControlCapability as Pt, humidifierCapability as Q, literal as Qt, connectivityCapability as R, weatherCapability as Rt, airQualitySensorCapability as S, runningFromMediaPlayer as St, automationControlCapability as T, scriptRunnerCapability as Tt, coverCapability as U, defineEntryKind as Ut, contactCapability as V, baseUnitOf as Vt, deviceAdminLinkCapability as W, defineRecordKinds as Wt, floodCapability as X, boolean as Xt, fanControlCapability as Y, array as Yt, gasCapability as Z, discriminatedUnion as Zt, PLUG_APPLIANCE_TYPES as _, publicEndpointReaderFrom as _t, BaseDevice as a, url as an, motionCapability as at, accessoriesCapability as b, runExportedAction as bt, COCO_TO_MACRO as c, DeviceType as cn, notifierCapability as ct, ConsumableItemSchema as d, parseAdminBase as dt, object as en, imageCapability as et, ConsumableStatusSchema as f, powerMeterCapability as ft, MediaPlayerRepeatSchema as g, pruneDeletedEntities as gt, HvacModeSchema as h, pressureSensorCapability as ht, AlarmArmModeSchema as i, unknown as in, mediaPlayerCapability as it, buildAddonRouteProvider as j, tryConvertDelta as jt, brightnessCapability as k, tamperCapability as kt, CameraSwitchIdSchema as l, errMsg as ln, numericSensorCapability as lt, FanDirectionSchema as m, presenceCapability as mt, HA_PROVIDER_ADDON_ID as n, string as nn, lawnMowerControlCapability as nt, BaseDeviceProvider as o, DeviceFeature as on, normalizeUnit as ot, EnumSensorDateTimeFormatSchema as p, prepareNotification as pt, enumSensorCapability as q, nodePin as qt, listHomeAssistantBrokers as r, union as rn, lockControlCapability as rt, CAMERA_SWITCH_ORDER as s, DeviceRole as sn, notificationOutputCapability as st, HA_BROKER_KIND as t, record as tn, ingestReading as tt, ConsumableFormSchema as u, EventCategory as un, oauthIntegrationCapability as ut, RUNNING_STATE_COLD as v, quantityFromUnit as vt, ambientLightSensorCapability as w, runningStateCapability as wt, addonRoutesCapability as x, runningFromLawnMower as xt, TargetSchema as y, resolveAttachmentUrls as yt, consumableFormOf as z, BaseAddon as zt };