@camstack/addon-provider-homeassistant 1.2.153 → 1.2.154

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("./dist-DMn4t1u_.js");
2
+ const require_dist = require("./dist-DLQgcqQT.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_zlib = require("node:zlib");
5
5
  //#region src/ha-admin-link.ts
@@ -5130,6 +5130,7 @@ var HaIntegrationManager = class HaIntegrationManager {
5130
5130
  if (target.deviceId) targetEnvelope["device_id"] = target.deviceId;
5131
5131
  if (Object.keys(targetEnvelope).length > 0) message["target"] = targetEnvelope;
5132
5132
  if (payload !== void 0 && payload !== null) message["service_data"] = payload;
5133
+ if (target.returnResponse === true) message["return_response"] = true;
5133
5134
  const result = await conn.sendMessagePromise(message);
5134
5135
  if (result && typeof result === "object") return result;
5135
5136
  return null;
@@ -7071,6 +7072,122 @@ function createHaNotificationOutputProvider(deps) {
7071
7072
  };
7072
7073
  }
7073
7074
  //#endregion
7075
+ //#region src/ha-calendar-source.ts
7076
+ function isCalendarEntity(entityId) {
7077
+ return entityId.startsWith("calendar.");
7078
+ }
7079
+ function calendarName(state) {
7080
+ const friendly = state.attributes["friendly_name"];
7081
+ if (typeof friendly === "string" && friendly.trim().length > 0) return friendly.trim();
7082
+ return state.entity_id.replace(/^calendar\./, "").replace(/_/g, " ");
7083
+ }
7084
+ function parseHaDate(value) {
7085
+ if (typeof value !== "string" || value.length === 0) return null;
7086
+ const ms = Date.parse(value.length === 10 ? `${value}T00:00:00` : value);
7087
+ return Number.isFinite(ms) ? ms : null;
7088
+ }
7089
+ function isRecord(x) {
7090
+ return typeof x === "object" && x !== null && !Array.isArray(x);
7091
+ }
7092
+ function asEntityState(raw) {
7093
+ const parsed = HaEntityStateSchema.safeParse(raw);
7094
+ return parsed.success ? parsed.data : null;
7095
+ }
7096
+ /**
7097
+ * HA's `calendar.list_events` response shape (return_response):
7098
+ * `{ response: { [entity_id]: { events: [...] } } }` or a flat
7099
+ * `{ events: [...] }` depending on target width.
7100
+ */
7101
+ function extractEvents(result, entityId) {
7102
+ if (!isRecord(result)) return [];
7103
+ const response = result["response"];
7104
+ if (isRecord(response)) {
7105
+ const forEntity = response[entityId];
7106
+ if (isRecord(forEntity) && Array.isArray(forEntity["events"])) return forEntity["events"].filter(isRecord);
7107
+ for (const value of Object.values(response)) if (isRecord(value) && Array.isArray(value["events"])) return value["events"].filter(isRecord);
7108
+ }
7109
+ if (Array.isArray(result["events"])) return result["events"].filter(isRecord);
7110
+ return [];
7111
+ }
7112
+ function mapEvent(entityId, raw, index) {
7113
+ const startRaw = raw["start"];
7114
+ const endRaw = raw["end"];
7115
+ const startMs = parseHaDate(isRecord(startRaw) ? startRaw["dateTime"] ?? startRaw["date"] : raw["start"] ?? raw["start_date_time"] ?? raw["dtstart"]);
7116
+ const endMs = parseHaDate(isRecord(endRaw) ? endRaw["dateTime"] ?? endRaw["date"] : raw["end"] ?? raw["end_date_time"] ?? raw["dtend"]) ?? startMs;
7117
+ if (startMs === null || endMs === null) return null;
7118
+ const summary = raw["summary"] ?? raw["title"];
7119
+ const title = typeof summary === "string" ? summary : "(no title)";
7120
+ const uid = raw["uid"] ?? raw["id"];
7121
+ const id = typeof uid === "string" && uid.length > 0 ? `${entityId}:${uid}` : `${entityId}:${String(startMs)}:${String(index)}`;
7122
+ const description = raw["description"];
7123
+ const location = raw["location"];
7124
+ return {
7125
+ id,
7126
+ calendarId: entityId,
7127
+ title,
7128
+ startMs,
7129
+ endMs,
7130
+ ...isRecord(startRaw) && typeof startRaw["date"] === "string" || typeof raw["start"] === "string" && raw["start"].length === 10 ? { allDay: true } : {},
7131
+ ...typeof description === "string" && description.length > 0 ? { description } : {},
7132
+ ...typeof location === "string" && location.length > 0 ? { location } : {}
7133
+ };
7134
+ }
7135
+ function createHaCalendarSource(deps) {
7136
+ return {
7137
+ listCalendars: async () => {
7138
+ const out = [];
7139
+ for (const { manager } of deps.brokers.listManagers()) {
7140
+ const states = manager.snapshotAllStates();
7141
+ for (const [entityId, raw] of Object.entries(states)) {
7142
+ if (!isCalendarEntity(entityId)) continue;
7143
+ const state = asEntityState(raw);
7144
+ if (state === null) continue;
7145
+ out.push({
7146
+ id: entityId,
7147
+ name: calendarName(state),
7148
+ source: "ha",
7149
+ enabled: state.state !== "unavailable"
7150
+ });
7151
+ }
7152
+ }
7153
+ return out;
7154
+ },
7155
+ listEvents: async (input) => {
7156
+ const startIso = new Date(input.startMs).toISOString();
7157
+ const endIso = new Date(input.endMs).toISOString();
7158
+ const wanted = input.calendarIds !== void 0 && input.calendarIds.length > 0 ? new Set(input.calendarIds) : null;
7159
+ const out = [];
7160
+ for (const { brokerId, manager } of deps.brokers.listManagers()) {
7161
+ const states = manager.snapshotAllStates();
7162
+ const entityIds = Object.keys(states).filter((id) => isCalendarEntity(id) && (wanted === null || wanted.has(id)));
7163
+ for (const entityId of entityIds) try {
7164
+ const target = {
7165
+ domain: "calendar",
7166
+ service: "list_events",
7167
+ entityId,
7168
+ returnResponse: true
7169
+ };
7170
+ const rawEvents = extractEvents(await manager.publish(target, {
7171
+ start_date_time: startIso,
7172
+ end_date_time: endIso
7173
+ }), entityId);
7174
+ for (let i = 0; i < rawEvents.length; i += 1) {
7175
+ const mapped = mapEvent(entityId, rawEvents[i], i);
7176
+ if (mapped !== null) out.push(mapped);
7177
+ }
7178
+ } catch (err) {
7179
+ deps.logger.warn("HA calendar.list_events failed", { meta: {
7180
+ entityId,
7181
+ brokerId,
7182
+ error: err instanceof Error ? err.message : String(err)
7183
+ } });
7184
+ }
7185
+ }
7186
+ return out.toSorted((a, b) => a.startMs - b.startMs);
7187
+ }
7188
+ };
7189
+ }
7190
+ //#endregion
7074
7191
  //#region src/ha-reconcile-broker.ts
7075
7192
  /**
7076
7193
  * Run BOTH reconcilers for one broker off a SINGLE shared inventory fetch.
@@ -7596,6 +7713,13 @@ var HaProviderAddon = class HaProviderAddon extends require_dist.BaseDeviceProvi
7596
7713
  {
7597
7714
  capability: require_dist.addonRoutesCapability,
7598
7715
  provider: createHaIconRouteProvider()
7716
+ },
7717
+ {
7718
+ capability: require_dist.calendarSourceCapability,
7719
+ provider: createHaCalendarSource({
7720
+ brokers: this.requireRegistry(),
7721
+ logger: this.ctx.logger.child("calendar-source")
7722
+ })
7599
7723
  }
7600
7724
  ];
7601
7725
  }
package/dist/addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as parseAdminBase, A as contactCapability, At as union, B as gasCapability, C as buildAddonRouteProvider, Ct as array, D as climateControlCapability, Dt as object, E as carbonMonoxideCapability, Et as number, G as lockControlCapability, H as humiditySensorCapability, I as enumSensorCapability, J as normalizeUnit, K as mediaPlayerCapability, L as eventEmitterCapability, M as coverCapability, Mt as url, N as deviceAdminLinkCapability, Nt as EventCategory, O as colorCapability, Ot as record, P as deviceAdoptionCapability, R as fanControlCapability, S as brokerCapability, St as _enum, T as buttonCapability, Tt as literal, U as imageCapability, V as humidifierCapability, W as lawnMowerControlCapability, X as notifierCapability, Y as notificationOutputCapability, Z as numericSensorCapability, _ as automationControlCapability, _t as DeviceType, at as smokeCapability, b as binaryCapability, bt as createEvent, ct as temperatureSensorCapability, d as TargetSchema, dt as valveCapability, et as powerMeterCapability, f as accessoriesCapability, ft as vibrationCapability, g as ambientLightSensorCapability, gt as DeviceRole, h as alarmPanelCapability, ht as DeviceFeature, it as scriptRunnerCapability, j as controlCapability, jt as unknown, k as connectivityCapability, kt as string, lt as updateCapability, m as airQualitySensorCapability, mt as weatherCapability, n as BaseDevice, nt as presenceCapability, ot as switchCapability, p as addonRoutesCapability, pt as waterHeaterCapability, q as motionCapability, r as BaseDeviceProvider, rt as pressureSensorCapability, s as EnumSensorDateTimeFormatSchema, st as tamperCapability, tt as prepareNotification, ut as vacuumControlCapability, v as batteryCapability, vt as errMsg, w as buildDeviceAdminUrl, wt as discriminatedUnion, x as brightnessCapability, y as bestLocationMatch, z as floodCapability } from "./dist-evNLjXgk.mjs";
1
+ import { A as connectivityCapability, At as string, B as floodCapability, C as buildAddonRouteProvider, Ct as _enum, D as carbonMonoxideCapability, Dt as number, E as calendarSourceCapability, Et as literal, F as deviceAdoptionCapability, G as lawnMowerControlCapability, H as humidifierCapability, J as motionCapability, K as lockControlCapability, L as enumSensorCapability, M as controlCapability, Mt as unknown, N as coverCapability, Nt as url, O as climateControlCapability, Ot as object, P as deviceAdminLinkCapability, Pt as EventCategory, Q as numericSensorCapability, R as eventEmitterCapability, S as brokerCapability, T as buttonCapability, Tt as discriminatedUnion, U as humiditySensorCapability, V as gasCapability, W as imageCapability, X as notificationOutputCapability, Y as normalizeUnit, Z as notifierCapability, _ as automationControlCapability, _t as DeviceRole, at as scriptRunnerCapability, b as binaryCapability, ct as tamperCapability, d as TargetSchema, dt as vacuumControlCapability, et as parseAdminBase, f as accessoriesCapability, ft as valveCapability, g as ambientLightSensorCapability, gt as DeviceFeature, h as alarmPanelCapability, ht as weatherCapability, it as pressureSensorCapability, j as contactCapability, jt as union, k as colorCapability, kt as record, lt as temperatureSensorCapability, m as airQualitySensorCapability, mt as waterHeaterCapability, n as BaseDevice, nt as prepareNotification, ot as smokeCapability, p as addonRoutesCapability, pt as vibrationCapability, q as mediaPlayerCapability, r as BaseDeviceProvider, rt as presenceCapability, s as EnumSensorDateTimeFormatSchema, st as switchCapability, tt as powerMeterCapability, ut as updateCapability, v as batteryCapability, vt as DeviceType, w as buildDeviceAdminUrl, wt as array, x as brightnessCapability, xt as createEvent, y as bestLocationMatch, yt as errMsg, z as fanControlCapability } from "./dist-CvzpCuzm.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
4
4
  //#region src/ha-admin-link.ts
@@ -5129,6 +5129,7 @@ var HaIntegrationManager = class HaIntegrationManager {
5129
5129
  if (target.deviceId) targetEnvelope["device_id"] = target.deviceId;
5130
5130
  if (Object.keys(targetEnvelope).length > 0) message["target"] = targetEnvelope;
5131
5131
  if (payload !== void 0 && payload !== null) message["service_data"] = payload;
5132
+ if (target.returnResponse === true) message["return_response"] = true;
5132
5133
  const result = await conn.sendMessagePromise(message);
5133
5134
  if (result && typeof result === "object") return result;
5134
5135
  return null;
@@ -7070,6 +7071,122 @@ function createHaNotificationOutputProvider(deps) {
7070
7071
  };
7071
7072
  }
7072
7073
  //#endregion
7074
+ //#region src/ha-calendar-source.ts
7075
+ function isCalendarEntity(entityId) {
7076
+ return entityId.startsWith("calendar.");
7077
+ }
7078
+ function calendarName(state) {
7079
+ const friendly = state.attributes["friendly_name"];
7080
+ if (typeof friendly === "string" && friendly.trim().length > 0) return friendly.trim();
7081
+ return state.entity_id.replace(/^calendar\./, "").replace(/_/g, " ");
7082
+ }
7083
+ function parseHaDate(value) {
7084
+ if (typeof value !== "string" || value.length === 0) return null;
7085
+ const ms = Date.parse(value.length === 10 ? `${value}T00:00:00` : value);
7086
+ return Number.isFinite(ms) ? ms : null;
7087
+ }
7088
+ function isRecord(x) {
7089
+ return typeof x === "object" && x !== null && !Array.isArray(x);
7090
+ }
7091
+ function asEntityState(raw) {
7092
+ const parsed = HaEntityStateSchema.safeParse(raw);
7093
+ return parsed.success ? parsed.data : null;
7094
+ }
7095
+ /**
7096
+ * HA's `calendar.list_events` response shape (return_response):
7097
+ * `{ response: { [entity_id]: { events: [...] } } }` or a flat
7098
+ * `{ events: [...] }` depending on target width.
7099
+ */
7100
+ function extractEvents(result, entityId) {
7101
+ if (!isRecord(result)) return [];
7102
+ const response = result["response"];
7103
+ if (isRecord(response)) {
7104
+ const forEntity = response[entityId];
7105
+ if (isRecord(forEntity) && Array.isArray(forEntity["events"])) return forEntity["events"].filter(isRecord);
7106
+ for (const value of Object.values(response)) if (isRecord(value) && Array.isArray(value["events"])) return value["events"].filter(isRecord);
7107
+ }
7108
+ if (Array.isArray(result["events"])) return result["events"].filter(isRecord);
7109
+ return [];
7110
+ }
7111
+ function mapEvent(entityId, raw, index) {
7112
+ const startRaw = raw["start"];
7113
+ const endRaw = raw["end"];
7114
+ const startMs = parseHaDate(isRecord(startRaw) ? startRaw["dateTime"] ?? startRaw["date"] : raw["start"] ?? raw["start_date_time"] ?? raw["dtstart"]);
7115
+ const endMs = parseHaDate(isRecord(endRaw) ? endRaw["dateTime"] ?? endRaw["date"] : raw["end"] ?? raw["end_date_time"] ?? raw["dtend"]) ?? startMs;
7116
+ if (startMs === null || endMs === null) return null;
7117
+ const summary = raw["summary"] ?? raw["title"];
7118
+ const title = typeof summary === "string" ? summary : "(no title)";
7119
+ const uid = raw["uid"] ?? raw["id"];
7120
+ const id = typeof uid === "string" && uid.length > 0 ? `${entityId}:${uid}` : `${entityId}:${String(startMs)}:${String(index)}`;
7121
+ const description = raw["description"];
7122
+ const location = raw["location"];
7123
+ return {
7124
+ id,
7125
+ calendarId: entityId,
7126
+ title,
7127
+ startMs,
7128
+ endMs,
7129
+ ...isRecord(startRaw) && typeof startRaw["date"] === "string" || typeof raw["start"] === "string" && raw["start"].length === 10 ? { allDay: true } : {},
7130
+ ...typeof description === "string" && description.length > 0 ? { description } : {},
7131
+ ...typeof location === "string" && location.length > 0 ? { location } : {}
7132
+ };
7133
+ }
7134
+ function createHaCalendarSource(deps) {
7135
+ return {
7136
+ listCalendars: async () => {
7137
+ const out = [];
7138
+ for (const { manager } of deps.brokers.listManagers()) {
7139
+ const states = manager.snapshotAllStates();
7140
+ for (const [entityId, raw] of Object.entries(states)) {
7141
+ if (!isCalendarEntity(entityId)) continue;
7142
+ const state = asEntityState(raw);
7143
+ if (state === null) continue;
7144
+ out.push({
7145
+ id: entityId,
7146
+ name: calendarName(state),
7147
+ source: "ha",
7148
+ enabled: state.state !== "unavailable"
7149
+ });
7150
+ }
7151
+ }
7152
+ return out;
7153
+ },
7154
+ listEvents: async (input) => {
7155
+ const startIso = new Date(input.startMs).toISOString();
7156
+ const endIso = new Date(input.endMs).toISOString();
7157
+ const wanted = input.calendarIds !== void 0 && input.calendarIds.length > 0 ? new Set(input.calendarIds) : null;
7158
+ const out = [];
7159
+ for (const { brokerId, manager } of deps.brokers.listManagers()) {
7160
+ const states = manager.snapshotAllStates();
7161
+ const entityIds = Object.keys(states).filter((id) => isCalendarEntity(id) && (wanted === null || wanted.has(id)));
7162
+ for (const entityId of entityIds) try {
7163
+ const target = {
7164
+ domain: "calendar",
7165
+ service: "list_events",
7166
+ entityId,
7167
+ returnResponse: true
7168
+ };
7169
+ const rawEvents = extractEvents(await manager.publish(target, {
7170
+ start_date_time: startIso,
7171
+ end_date_time: endIso
7172
+ }), entityId);
7173
+ for (let i = 0; i < rawEvents.length; i += 1) {
7174
+ const mapped = mapEvent(entityId, rawEvents[i], i);
7175
+ if (mapped !== null) out.push(mapped);
7176
+ }
7177
+ } catch (err) {
7178
+ deps.logger.warn("HA calendar.list_events failed", { meta: {
7179
+ entityId,
7180
+ brokerId,
7181
+ error: err instanceof Error ? err.message : String(err)
7182
+ } });
7183
+ }
7184
+ }
7185
+ return out.toSorted((a, b) => a.startMs - b.startMs);
7186
+ }
7187
+ };
7188
+ }
7189
+ //#endregion
7073
7190
  //#region src/ha-reconcile-broker.ts
7074
7191
  /**
7075
7192
  * Run BOTH reconcilers for one broker off a SINGLE shared inventory fetch.
@@ -7595,6 +7712,13 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
7595
7712
  {
7596
7713
  capability: addonRoutesCapability,
7597
7714
  provider: createHaIconRouteProvider()
7715
+ },
7716
+ {
7717
+ capability: calendarSourceCapability,
7718
+ provider: createHaCalendarSource({
7719
+ brokers: this.requireRegistry(),
7720
+ logger: this.ctx.logger.child("calendar-source")
7721
+ })
7598
7722
  }
7599
7723
  ];
7600
7724
  }
@@ -20687,14 +20687,52 @@ var NcAlarmNonBlockingEditSchema = object({
20687
20687
  nonBlocking: boolean()
20688
20688
  });
20689
20689
  /**
20690
+ * Controllable openings: opt IN to blocking (default is do not block). Contacts
20691
+ * keep using {@link NcAlarmNonBlockingEditSchema}.
20692
+ */
20693
+ var NcAlarmBlockingEditSchema = object({
20694
+ mode: AlarmArmModeSchema,
20695
+ deviceId: number().int().positive(),
20696
+ /** true = on the list (blocks when open); false = off the list (default). */
20697
+ blocking: boolean()
20698
+ });
20699
+ /**
20700
+ * Controllable openings: opt OUT of secure-on-arm (default is secure ON —
20701
+ * close / lock / turn off when this mode arms).
20702
+ */
20703
+ var NcAlarmSecureOnArmEditSchema = object({
20704
+ mode: AlarmArmModeSchema,
20705
+ deviceId: number().int().positive(),
20706
+ /** true = secure on arm (default); false = do not actuate on arm. */
20707
+ secureOnArm: boolean()
20708
+ });
20709
+ /**
20710
+ * One arm mode's device-id list (non-blocking, blocking, or secure-on-arm-off).
20711
+ */
20712
+ var NcAlarmModeDeviceListSchema = object({
20713
+ mode: AlarmArmModeSchema,
20714
+ deviceIds: array(number().int()).max(200)
20715
+ });
20716
+ /**
20717
+ * One arm mode's non-blocking list: devices that may be open when that mode
20718
+ * arms, without the arm being refused.
20719
+ */
20720
+ var NcAlarmNonBlockingSchema = NcAlarmModeDeviceListSchema;
20721
+ /** Controllable devices that DO block when open (opt-in; default empty). */
20722
+ var NcAlarmBlockingSchema = NcAlarmModeDeviceListSchema;
20723
+ /**
20724
+ * Controllable devices that do NOT secure on arm (opt-out; default empty =
20725
+ * every controllable secures).
20726
+ */
20727
+ var NcAlarmSecureOnArmOffSchema = NcAlarmModeDeviceListSchema;
20728
+ /**
20690
20729
  * The per-mode lists, at most one per mode. Two lists for one mode have no
20691
20730
  * meaning a reader could agree on (union? the last one?), so they are refused
20692
20731
  * at the boundary rather than interpreted.
20693
20732
  */
20694
- var NcAlarmNonBlockingListSchema = array(object({
20695
- mode: AlarmArmModeSchema,
20696
- deviceIds: array(number().int()).max(200)
20697
- })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20733
+ var NcAlarmNonBlockingListSchema = array(NcAlarmNonBlockingSchema).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20734
+ var NcAlarmBlockingListSchema = array(NcAlarmBlockingSchema).max(8).refine(uniqueModes, { message: "blocking: at most one list per arm mode" });
20735
+ var NcAlarmSecureOnArmOffListSchema = array(NcAlarmSecureOnArmOffSchema).max(8).refine(uniqueModes, { message: "secureOnArmOff: at most one list per arm mode" });
20698
20736
  /**
20699
20737
  * The three durations — global, and per mode since D697 — the panel's state
20700
20738
  * machine runs on, who hears about its transitions, and which openings each mode tolerates.
@@ -20737,9 +20775,23 @@ var NcAlarmSettingsSchema = object({
20737
20775
  * arm (a window left ajar for the cat under `home`, say). At most one entry
20738
20776
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20739
20777
  * parses as "every opening blocks" — the behaviour it was written under.
20778
+ * Contacts use this list; controllable openings use {@link blocking} instead
20779
+ * (default: do not block).
20740
20780
  */
20741
20781
  nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20742
20782
  /**
20783
+ * Per arm mode, controllable openings that DO refuse the arm when open.
20784
+ * Default `[]` = none block (secure-on-arm closes them instead). Absent on a
20785
+ * blob older than this field — read as empty.
20786
+ */
20787
+ blocking: NcAlarmBlockingListSchema.default([]),
20788
+ /**
20789
+ * Per arm mode, controllable openings that must NOT be actuated on arm.
20790
+ * Default `[]` = every controllable secures (close / lock / off). Absent on
20791
+ * an older blob — read as empty (secure ON).
20792
+ */
20793
+ secureOnArmOff: NcAlarmSecureOnArmOffListSchema.default([]),
20794
+ /**
20743
20795
  * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20744
20796
  * without a field, uses the global value above — so `[]`, the default, is exactly the
20745
20797
  * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
@@ -20749,7 +20801,7 @@ var NcAlarmSettingsSchema = object({
20749
20801
  /**
20750
20802
  * Every field optional — a tab edits one control at a time.
20751
20803
  *
20752
- * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20804
+ * List fields and `modeDelays` are re-declared WITHOUT their defaults:
20753
20805
  * `.partial()` does not remove an inner `.default()`, so a patch that never
20754
20806
  * named the field would parse to `[]` and wipe every mode's list on an
20755
20807
  * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
@@ -20757,11 +20809,15 @@ var NcAlarmSettingsSchema = object({
20757
20809
  */
20758
20810
  var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20759
20811
  nonBlocking: NcAlarmNonBlockingListSchema,
20812
+ blocking: NcAlarmBlockingListSchema,
20813
+ secureOnArmOff: NcAlarmSecureOnArmOffListSchema,
20760
20814
  /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20761
20815
  modeDelays: NcAlarmModeDelaysEditListSchema,
20762
20816
  /** ONE membership change; the panel applies it to the list it holds at its turn. */
20763
- nonBlockingEdit: NcAlarmNonBlockingEditSchema
20764
- }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20817
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema,
20818
+ blockingEdit: NcAlarmBlockingEditSchema,
20819
+ secureOnArmEdit: NcAlarmSecureOnArmEditSchema
20820
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" }).refine((p) => p.blocking === void 0 || p.blockingEdit === void 0, { message: "blocking and blockingEdit are exclusive — send the whole lists or one edit" }).refine((p) => p.secureOnArmOff === void 0 || p.secureOnArmEdit === void 0, { message: "secureOnArmOff and secureOnArmEdit are exclusive — send the whole lists or one edit" });
20765
20821
  /**
20766
20822
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20767
20823
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20876,7 +20932,13 @@ var NcAlarmSensorStateSchema = object({
20876
20932
  /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20877
20933
  unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20878
20934
  /** The device manager's liveness as the centre mirrors it; null = not known. */
20879
- online: boolean().nullable()
20935
+ online: boolean().nullable(),
20936
+ /**
20937
+ * True when the opening is a cover / lock / switch — the table shows the
20938
+ * Secure-on-arm switch and uses the controllable defaults. False for contacts
20939
+ * and for non-openings. Defaulted so an older hub answer still parses.
20940
+ */
20941
+ controllable: boolean().default(false)
20880
20942
  });
20881
20943
  var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20882
20944
  var NcAlarmSensorsSchema = object({
@@ -29395,6 +29457,74 @@ var buttonCapability = {
29395
29457
  }) }
29396
29458
  };
29397
29459
  /**
29460
+ * `calendar-source` — collection cap exposing per-provider calendars and
29461
+ * their events. Every addon that contributes calendars (Home Assistant,
29462
+ * a future Google Calendar addon, …) registers a provider here. The hub
29463
+ * singleton `calendars` walks the collection and merges the answers.
29464
+ *
29465
+ * Mirrors `addon-pages-source`: the public listing is a different shape
29466
+ * (source-stamped ids) and lives on the aggregator.
29467
+ */
29468
+ var CalendarSourceKindSchema = _enum(["ha", "addon"]);
29469
+ var CalendarInfoSchema = object({
29470
+ /** Provider-local id (e.g. HA `calendar.family`). The aggregator prefixes. */
29471
+ id: string().min(1),
29472
+ name: string().min(1),
29473
+ source: CalendarSourceKindSchema,
29474
+ enabled: boolean()
29475
+ });
29476
+ var CalendarEventSchema = object({
29477
+ id: string().min(1),
29478
+ calendarId: string().min(1),
29479
+ title: string(),
29480
+ startMs: number().int(),
29481
+ endMs: number().int(),
29482
+ allDay: boolean().optional(),
29483
+ description: string().optional(),
29484
+ location: string().optional()
29485
+ });
29486
+ var CalendarListEventsInputSchema = object({
29487
+ calendarIds: array(string().min(1)).max(64).optional(),
29488
+ startMs: number().int(),
29489
+ endMs: number().int()
29490
+ });
29491
+ var calendarSourceCapability = {
29492
+ name: "calendar-source",
29493
+ scope: "system",
29494
+ mode: "collection",
29495
+ internal: true,
29496
+ methods: {
29497
+ listCalendars: method(_void(), array(CalendarInfoSchema).readonly(), { auth: "admin" }),
29498
+ listEvents: method(CalendarListEventsInputSchema, array(CalendarEventSchema).readonly(), { auth: "admin" })
29499
+ }
29500
+ };
29501
+ /**
29502
+ * `calendars` — system-scoped singleton. Public surface admin-ui and the
29503
+ * notification-center calendar-events producer consume.
29504
+ *
29505
+ * Walks every `calendar-source` provider, stamps each calendar with a
29506
+ * cluster-stable id (`<sourceAddonId>:<localId>`) and the providing
29507
+ * addon's id, and fans `listEvents` out the same way. Home Assistant
29508
+ * import is a built-in refresh of the HA source rather than a second
29509
+ * registry — `importFromHomeAssistant` asks that source to re-discover.
29510
+ */
29511
+ var CalendarEntrySchema = CalendarInfoSchema.extend({
29512
+ /** Cluster-stable id: `<providerAddonId>:<localId>`. */
29513
+ id: string().min(1),
29514
+ /** The addon that registered the calendar-source provider. */
29515
+ providerAddonId: string().min(1).optional()
29516
+ });
29517
+ var CalendarImportResultSchema = object({
29518
+ imported: number().int().nonnegative(),
29519
+ /** Absent when no HA calendar-source provider was reachable. */
29520
+ providerAddonId: string().min(1).optional(),
29521
+ error: string().optional()
29522
+ });
29523
+ method(_void(), array(CalendarEntrySchema).readonly(), { auth: "admin" }), method(CalendarListEventsInputSchema, array(CalendarEventSchema).readonly(), { auth: "admin" }), method(_void(), CalendarImportResultSchema, {
29524
+ auth: "admin",
29525
+ kind: "mutation"
29526
+ });
29527
+ /**
29398
29528
  * camera-credentials — device-scoped cap exposing the camera's network
29399
29529
  * + auth surface in a vendor-neutral shape.
29400
29530
  *
@@ -33943,38 +34073,52 @@ DeviceType.Camera, method(object({
33943
34073
  deviceId: number(),
33944
34074
  status: OsdStatusSchema
33945
34075
  });
34076
+ _enum([
34077
+ "value",
34078
+ "unit",
34079
+ "full"
34080
+ ]);
34081
+ _enum([
34082
+ "value+unit",
34083
+ "value",
34084
+ "unit"
34085
+ ]);
34086
+ /**
34087
+ * How the operator reached the leaf device. `deviceId` is ALWAYS the
34088
+ * OSD-supported leaf that is read; `containerId` is set when the pick
34089
+ * went through a container (gateway) first.
34090
+ */
34091
+ var OsdTemplatePickSchema = object({
34092
+ containerId: number().int().optional(),
34093
+ deviceId: number().int()
34094
+ });
33946
34095
  /**
33947
- * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
33948
- *
33949
- * The `osd` cap is the firmware contract: it probes a camera's overlay
33950
- * SLOTS and writes literal text into one. It has no idea WHERE that text
33951
- * comes from, and it must not — a driver that grew a "show the temperature
33952
- * here" feature would grow it once per vendor.
33953
- *
33954
- * This cap owns the other half: a per-(camera, slot) BINDING that says
33955
- * which value feeds the slot, how it is formatted, and under which
33956
- * conditions it is shown at all. One addon renders every binding on every
33957
- * camera, so a new source costs zero driver code.
33958
- *
33959
- * Three deliberate choices, each with a rejected alternative:
33960
- *
33961
- * 1. A source is `(capName, valuePath)` over the kernel's device
33962
- * runtime-state mirror — NOT a closed enum of source kinds. Every
33963
- * cap-keyed slice a device publishes is bindable the day the cap
33964
- * ships. The rejected alternative (one enum member per source, with
33965
- * a resolver branch each) is what makes "add the humidity too" a
33966
- * code change.
33967
- * 2. The display gate reuses `NcConditionsSchema` verbatim — the
33968
- * notification centre's condition vocabulary — rather than a parallel
33969
- * model. An operator who has learned one condition editor has learned
33970
- * both.
33971
- * 3. Because the renderer's facts are device STATE and not a detection
33972
- * record, only a SUBSET of that vocabulary can be answered here.
33973
- * `setSlotBinding` REJECTS the rest at write time (see
33974
- * `getConditionSupport`). It does not accept-then-fail-closed: a
33975
- * condition that can never be true renders a permanently blank
33976
- * overlay, and a blank overlay looks exactly like a broken camera.
34096
+ * One named binding inside a multi-device template: device path + unit
34097
+ * selection + precision. The parser expands `{alias}`, `{alias.value}`,
34098
+ * `{alias.unit}`, or `{alias.full}` from the same binding — there is no
34099
+ * per-binding format lock.
33977
34100
  */
34101
+ var OsdTemplateBindingSchema = object({
34102
+ /** Token stem in the parser, e.g. `t` → `{t}` / `{t.value}`. Unique within the template. */
34103
+ alias: string().min(1).max(32).regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "alias must start with a letter"),
34104
+ pick: OsdTemplatePickSchema,
34105
+ capName: string().min(1).max(64),
34106
+ valuePath: string().min(1).max(64),
34107
+ /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
34108
+ displayUnit: string().max(16).optional(),
34109
+ maxDecimals: number().int().min(0).max(4).default(1)
34110
+ });
34111
+ /**
34112
+ * A named template: bindings (alias → leaf field) + a parser that
34113
+ * references `{alias}` / `{alias.value}` / `{alias.unit}` / `{alias.full}`.
34114
+ */
34115
+ var OsdNamedTemplateSchema = object({
34116
+ id: string().min(1).max(64),
34117
+ name: string().min(1).max(64),
34118
+ bindings: array(OsdTemplateBindingSchema).max(32).default([]),
34119
+ /** Parser with alias placeholders, e.g. `{t.value} {t.unit} / {h}`. */
34120
+ parser: string().max(256).default("")
34121
+ });
33978
34122
  /** Where a slot's value comes from. */
33979
34123
  var OsdSourceSchema = discriminatedUnion("kind", [
33980
34124
  object({
@@ -33993,11 +34137,17 @@ var OsdSourceSchema = discriminatedUnion("kind", [
33993
34137
  deviceId: number().int().optional(),
33994
34138
  capName: string().min(1).max(64),
33995
34139
  /** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
33996
- valuePath: string().min(1).max(64)
34140
+ valuePath: string().min(1).max(64),
34141
+ /** Convert TO this unit at render. Absent = no conversion. */
34142
+ displayUnit: string().max(16).optional()
33997
34143
  }),
33998
34144
  object({
33999
34145
  kind: literal("latest-recognition"),
34000
34146
  recognition: _enum(["person", "plate"])
34147
+ }),
34148
+ object({
34149
+ kind: literal("template"),
34150
+ templateId: string().min(1).max(64)
34001
34151
  })
34002
34152
  ]);
34003
34153
  var OsdSlotBindingSchema = object({
@@ -34013,7 +34163,10 @@ var OsdSlotBindingSchema = object({
34013
34163
  * documented workaround for firmwares that reject `.` in overlay text.
34014
34164
  */
34015
34165
  maxDecimals: number().int().min(0).max(4).default(1),
34016
- /** Appended via `${unit}`. The state mirror does not carry units. */
34166
+ /**
34167
+ * Appended via `${unit}` when no unit conversion produces a label.
34168
+ * Prefer `source.displayUnit` for converted device-state values.
34169
+ */
34017
34170
  unitLabel: string().max(8).optional(),
34018
34171
  /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
34019
34172
  valueMap: record(string(), string()).optional(),
@@ -34122,6 +34275,12 @@ method(object({ deviceId: number().int() }), object({
34122
34275
  }), method(object({ deviceId: number().int() }), object({ results: array(OsdRenderResultSchema) }), {
34123
34276
  kind: "mutation",
34124
34277
  auth: "admin"
34278
+ }), method(object({}), object({ templates: array(OsdNamedTemplateSchema) }), { auth: "admin" }), method(object({ template: OsdNamedTemplateSchema }), object({ template: OsdNamedTemplateSchema }), {
34279
+ kind: "mutation",
34280
+ auth: "admin"
34281
+ }), method(object({ templateId: string().min(1).max(64) }), object({ success: literal(true) }), {
34282
+ kind: "mutation",
34283
+ auth: "admin"
34125
34284
  });
34126
34285
  /**
34127
34286
  * Feeder connectivity / power status — mirrors the HA petkit device-status
@@ -42916,6 +43075,36 @@ Object.freeze({
42916
43075
  addonId: null,
42917
43076
  access: "create"
42918
43077
  },
43078
+ "calendars.importFromHomeAssistant": {
43079
+ capName: "calendars",
43080
+ capScope: "system",
43081
+ addonId: null,
43082
+ access: "create"
43083
+ },
43084
+ "calendars.listCalendars": {
43085
+ capName: "calendars",
43086
+ capScope: "system",
43087
+ addonId: null,
43088
+ access: "view"
43089
+ },
43090
+ "calendars.listEvents": {
43091
+ capName: "calendars",
43092
+ capScope: "system",
43093
+ addonId: null,
43094
+ access: "view"
43095
+ },
43096
+ "calendarSource.listCalendars": {
43097
+ capName: "calendar-source",
43098
+ capScope: "system",
43099
+ addonId: null,
43100
+ access: "view"
43101
+ },
43102
+ "calendarSource.listEvents": {
43103
+ capName: "calendar-source",
43104
+ capScope: "system",
43105
+ addonId: null,
43106
+ access: "view"
43107
+ },
42919
43108
  "cameraCredentials.getCredentials": {
42920
43109
  capName: "camera-credentials",
42921
43110
  capScope: "device",
@@ -45910,6 +46099,12 @@ Object.freeze({
45910
46099
  addonId: null,
45911
46100
  access: "create"
45912
46101
  },
46102
+ "osdManager.deleteTemplate": {
46103
+ capName: "osd-manager",
46104
+ capScope: "system",
46105
+ addonId: null,
46106
+ access: "delete"
46107
+ },
45913
46108
  "osdManager.getConditionSupport": {
45914
46109
  capName: "osd-manager",
45915
46110
  capScope: "system",
@@ -45928,6 +46123,12 @@ Object.freeze({
45928
46123
  addonId: null,
45929
46124
  access: "view"
45930
46125
  },
46126
+ "osdManager.listTemplates": {
46127
+ capName: "osd-manager",
46128
+ capScope: "system",
46129
+ addonId: null,
46130
+ access: "view"
46131
+ },
45931
46132
  "osdManager.previewSlot": {
45932
46133
  capName: "osd-manager",
45933
46134
  capScope: "system",
@@ -45946,6 +46147,12 @@ Object.freeze({
45946
46147
  addonId: null,
45947
46148
  access: "create"
45948
46149
  },
46150
+ "osdManager.upsertTemplate": {
46151
+ capName: "osd-manager",
46152
+ capScope: "system",
46153
+ addonId: null,
46154
+ access: "create"
46155
+ },
45949
46156
  "petFeeder.callPet": {
45950
46157
  capName: "pet-feeder",
45951
46158
  capScope: "device",
@@ -53120,4 +53327,4 @@ function bestLocationMatch(externalName, existing, threshold = .8) {
53120
53327
  var MB = 1024 * 1024;
53121
53328
  1024 * MB, 3072 * MB;
53122
53329
  //#endregion
53123
- export { parseAdminBase as $, contactCapability as A, union as At, gasCapability as B, buildAddonRouteProvider as C, array as Ct, climateControlCapability as D, object as Dt, carbonMonoxideCapability as E, number as Et, deviceExportCapability as F, lockControlCapability as G, humiditySensorCapability as H, enumSensorCapability as I, normalizeUnit as J, mediaPlayerCapability as K, eventEmitterCapability as L, coverCapability as M, url as Mt, deviceAdminLinkCapability as N, EventCategory as Nt, colorCapability as O, record as Ot, deviceAdoptionCapability as P, oauthIntegrationCapability as Q, fanControlCapability as R, brokerCapability as S, _enum as St, buttonCapability as T, literal as Tt, imageCapability as U, humidifierCapability as V, lawnMowerControlCapability as W, notifierCapability as X, notificationOutputCapability as Y, numericSensorCapability as Z, automationControlCapability as _, DeviceType as _t, COCO_TO_MACRO as a, smokeCapability as at, binaryCapability as b, createEvent as bt, FanDirectionSchema as c, temperatureSensorCapability as ct, TargetSchema as d, valveCapability as dt, powerMeterCapability as et, accessoriesCapability as f, vibrationCapability as ft, ambientLightSensorCapability as g, DeviceRole as gt, alarmPanelCapability as h, DeviceFeature as ht, CAMERA_SWITCH_ORDER as i, scriptRunnerCapability as it, controlCapability as j, unknown as jt, connectivityCapability as k, string as kt, HvacModeSchema as l, updateCapability as lt, airQualitySensorCapability as m, weatherCapability as mt, BaseDevice as n, presenceCapability as nt, CameraSwitchIdSchema as o, switchCapability as ot, addonRoutesCapability as p, waterHeaterCapability as pt, motionCapability as q, BaseDeviceProvider as r, pressureSensorCapability as rt, EnumSensorDateTimeFormatSchema as s, tamperCapability as st, AlarmArmModeSchema as t, prepareNotification as tt, MediaPlayerRepeatSchema as u, vacuumControlCapability as ut, batteryCapability as v, errMsg as vt, buildDeviceAdminUrl as w, discriminatedUnion as wt, brightnessCapability as x, nodePin as xt, bestLocationMatch as y, BaseAddon as yt, floodCapability as z };
53330
+ export { oauthIntegrationCapability as $, connectivityCapability as A, string as At, floodCapability as B, buildAddonRouteProvider as C, _enum as Ct, carbonMonoxideCapability as D, number as Dt, calendarSourceCapability as E, literal as Et, deviceAdoptionCapability as F, lawnMowerControlCapability as G, humidifierCapability as H, deviceExportCapability as I, motionCapability as J, lockControlCapability as K, enumSensorCapability as L, controlCapability as M, unknown as Mt, coverCapability as N, url as Nt, climateControlCapability as O, object as Ot, deviceAdminLinkCapability as P, EventCategory as Pt, numericSensorCapability as Q, eventEmitterCapability as R, brokerCapability as S, nodePin as St, buttonCapability as T, discriminatedUnion as Tt, humiditySensorCapability as U, gasCapability as V, imageCapability as W, notificationOutputCapability as X, normalizeUnit as Y, notifierCapability as Z, automationControlCapability as _, DeviceRole as _t, COCO_TO_MACRO as a, scriptRunnerCapability as at, binaryCapability as b, BaseAddon as bt, FanDirectionSchema as c, tamperCapability as ct, TargetSchema as d, vacuumControlCapability as dt, parseAdminBase as et, accessoriesCapability as f, valveCapability as ft, ambientLightSensorCapability as g, DeviceFeature as gt, alarmPanelCapability as h, weatherCapability as ht, CAMERA_SWITCH_ORDER as i, pressureSensorCapability as it, contactCapability as j, union as jt, colorCapability as k, record as kt, HvacModeSchema as l, temperatureSensorCapability as lt, airQualitySensorCapability as m, waterHeaterCapability as mt, BaseDevice as n, prepareNotification as nt, CameraSwitchIdSchema as o, smokeCapability as ot, addonRoutesCapability as p, vibrationCapability as pt, mediaPlayerCapability as q, BaseDeviceProvider as r, presenceCapability as rt, EnumSensorDateTimeFormatSchema as s, switchCapability as st, AlarmArmModeSchema as t, powerMeterCapability as tt, MediaPlayerRepeatSchema as u, updateCapability as ut, batteryCapability as v, DeviceType as vt, buildDeviceAdminUrl as w, array as wt, brightnessCapability as x, createEvent as xt, bestLocationMatch as y, errMsg as yt, fanControlCapability as z };
@@ -20688,14 +20688,52 @@ var NcAlarmNonBlockingEditSchema = object({
20688
20688
  nonBlocking: boolean()
20689
20689
  });
20690
20690
  /**
20691
+ * Controllable openings: opt IN to blocking (default is do not block). Contacts
20692
+ * keep using {@link NcAlarmNonBlockingEditSchema}.
20693
+ */
20694
+ var NcAlarmBlockingEditSchema = object({
20695
+ mode: AlarmArmModeSchema,
20696
+ deviceId: number().int().positive(),
20697
+ /** true = on the list (blocks when open); false = off the list (default). */
20698
+ blocking: boolean()
20699
+ });
20700
+ /**
20701
+ * Controllable openings: opt OUT of secure-on-arm (default is secure ON —
20702
+ * close / lock / turn off when this mode arms).
20703
+ */
20704
+ var NcAlarmSecureOnArmEditSchema = object({
20705
+ mode: AlarmArmModeSchema,
20706
+ deviceId: number().int().positive(),
20707
+ /** true = secure on arm (default); false = do not actuate on arm. */
20708
+ secureOnArm: boolean()
20709
+ });
20710
+ /**
20711
+ * One arm mode's device-id list (non-blocking, blocking, or secure-on-arm-off).
20712
+ */
20713
+ var NcAlarmModeDeviceListSchema = object({
20714
+ mode: AlarmArmModeSchema,
20715
+ deviceIds: array(number().int()).max(200)
20716
+ });
20717
+ /**
20718
+ * One arm mode's non-blocking list: devices that may be open when that mode
20719
+ * arms, without the arm being refused.
20720
+ */
20721
+ var NcAlarmNonBlockingSchema = NcAlarmModeDeviceListSchema;
20722
+ /** Controllable devices that DO block when open (opt-in; default empty). */
20723
+ var NcAlarmBlockingSchema = NcAlarmModeDeviceListSchema;
20724
+ /**
20725
+ * Controllable devices that do NOT secure on arm (opt-out; default empty =
20726
+ * every controllable secures).
20727
+ */
20728
+ var NcAlarmSecureOnArmOffSchema = NcAlarmModeDeviceListSchema;
20729
+ /**
20691
20730
  * The per-mode lists, at most one per mode. Two lists for one mode have no
20692
20731
  * meaning a reader could agree on (union? the last one?), so they are refused
20693
20732
  * at the boundary rather than interpreted.
20694
20733
  */
20695
- var NcAlarmNonBlockingListSchema = array(object({
20696
- mode: AlarmArmModeSchema,
20697
- deviceIds: array(number().int()).max(200)
20698
- })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20734
+ var NcAlarmNonBlockingListSchema = array(NcAlarmNonBlockingSchema).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
20735
+ var NcAlarmBlockingListSchema = array(NcAlarmBlockingSchema).max(8).refine(uniqueModes, { message: "blocking: at most one list per arm mode" });
20736
+ var NcAlarmSecureOnArmOffListSchema = array(NcAlarmSecureOnArmOffSchema).max(8).refine(uniqueModes, { message: "secureOnArmOff: at most one list per arm mode" });
20699
20737
  /**
20700
20738
  * The three durations — global, and per mode since D697 — the panel's state
20701
20739
  * machine runs on, who hears about its transitions, and which openings each mode tolerates.
@@ -20738,9 +20776,23 @@ var NcAlarmSettingsSchema = object({
20738
20776
  * arm (a window left ajar for the cat under `home`, say). At most one entry
20739
20777
  * per mode is meaningful. Defaulted to `[]` so a blob stored before D630
20740
20778
  * parses as "every opening blocks" — the behaviour it was written under.
20779
+ * Contacts use this list; controllable openings use {@link blocking} instead
20780
+ * (default: do not block).
20741
20781
  */
20742
20782
  nonBlocking: NcAlarmNonBlockingListSchema.default([]),
20743
20783
  /**
20784
+ * Per arm mode, controllable openings that DO refuse the arm when open.
20785
+ * Default `[]` = none block (secure-on-arm closes them instead). Absent on a
20786
+ * blob older than this field — read as empty.
20787
+ */
20788
+ blocking: NcAlarmBlockingListSchema.default([]),
20789
+ /**
20790
+ * Per arm mode, controllable openings that must NOT be actuated on arm.
20791
+ * Default `[]` = every controllable secures (close / lock / off). Absent on
20792
+ * an older blob — read as empty (secure ON).
20793
+ */
20794
+ secureOnArmOff: NcAlarmSecureOnArmOffListSchema.default([]),
20795
+ /**
20744
20796
  * Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
20745
20797
  * without a field, uses the global value above — so `[]`, the default, is exactly the
20746
20798
  * behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
@@ -20750,7 +20802,7 @@ var NcAlarmSettingsSchema = object({
20750
20802
  /**
20751
20803
  * Every field optional — a tab edits one control at a time.
20752
20804
  *
20753
- * `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
20805
+ * List fields and `modeDelays` are re-declared WITHOUT their defaults:
20754
20806
  * `.partial()` does not remove an inner `.default()`, so a patch that never
20755
20807
  * named the field would parse to `[]` and wipe every mode's list on an
20756
20808
  * unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
@@ -20758,11 +20810,15 @@ var NcAlarmSettingsSchema = object({
20758
20810
  */
20759
20811
  var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
20760
20812
  nonBlocking: NcAlarmNonBlockingListSchema,
20813
+ blocking: NcAlarmBlockingListSchema,
20814
+ secureOnArmOff: NcAlarmSecureOnArmOffListSchema,
20761
20815
  /** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
20762
20816
  modeDelays: NcAlarmModeDelaysEditListSchema,
20763
20817
  /** ONE membership change; the panel applies it to the list it holds at its turn. */
20764
- nonBlockingEdit: NcAlarmNonBlockingEditSchema
20765
- }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
20818
+ nonBlockingEdit: NcAlarmNonBlockingEditSchema,
20819
+ blockingEdit: NcAlarmBlockingEditSchema,
20820
+ secureOnArmEdit: NcAlarmSecureOnArmEditSchema
20821
+ }).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" }).refine((p) => p.blocking === void 0 || p.blockingEdit === void 0, { message: "blocking and blockingEdit are exclusive — send the whole lists or one edit" }).refine((p) => p.secureOnArmOff === void 0 || p.secureOnArmEdit === void 0, { message: "secureOnArmOff and secureOnArmEdit are exclusive — send the whole lists or one edit" });
20766
20822
  /**
20767
20823
  * What one arm mode actually arms, DERIVED from the enabled rules gated on it.
20768
20824
  * Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
@@ -20877,7 +20933,13 @@ var NcAlarmSensorStateSchema = object({
20877
20933
  /** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
20878
20934
  unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
20879
20935
  /** The device manager's liveness as the centre mirrors it; null = not known. */
20880
- online: boolean().nullable()
20936
+ online: boolean().nullable(),
20937
+ /**
20938
+ * True when the opening is a cover / lock / switch — the table shows the
20939
+ * Secure-on-arm switch and uses the controllable defaults. False for contacts
20940
+ * and for non-openings. Defaulted so an older hub answer still parses.
20941
+ */
20942
+ controllable: boolean().default(false)
20881
20943
  });
20882
20944
  var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
20883
20945
  var NcAlarmSensorsSchema = object({
@@ -29396,6 +29458,74 @@ var buttonCapability = {
29396
29458
  }) }
29397
29459
  };
29398
29460
  /**
29461
+ * `calendar-source` — collection cap exposing per-provider calendars and
29462
+ * their events. Every addon that contributes calendars (Home Assistant,
29463
+ * a future Google Calendar addon, …) registers a provider here. The hub
29464
+ * singleton `calendars` walks the collection and merges the answers.
29465
+ *
29466
+ * Mirrors `addon-pages-source`: the public listing is a different shape
29467
+ * (source-stamped ids) and lives on the aggregator.
29468
+ */
29469
+ var CalendarSourceKindSchema = _enum(["ha", "addon"]);
29470
+ var CalendarInfoSchema = object({
29471
+ /** Provider-local id (e.g. HA `calendar.family`). The aggregator prefixes. */
29472
+ id: string().min(1),
29473
+ name: string().min(1),
29474
+ source: CalendarSourceKindSchema,
29475
+ enabled: boolean()
29476
+ });
29477
+ var CalendarEventSchema = object({
29478
+ id: string().min(1),
29479
+ calendarId: string().min(1),
29480
+ title: string(),
29481
+ startMs: number().int(),
29482
+ endMs: number().int(),
29483
+ allDay: boolean().optional(),
29484
+ description: string().optional(),
29485
+ location: string().optional()
29486
+ });
29487
+ var CalendarListEventsInputSchema = object({
29488
+ calendarIds: array(string().min(1)).max(64).optional(),
29489
+ startMs: number().int(),
29490
+ endMs: number().int()
29491
+ });
29492
+ var calendarSourceCapability = {
29493
+ name: "calendar-source",
29494
+ scope: "system",
29495
+ mode: "collection",
29496
+ internal: true,
29497
+ methods: {
29498
+ listCalendars: method(_void(), array(CalendarInfoSchema).readonly(), { auth: "admin" }),
29499
+ listEvents: method(CalendarListEventsInputSchema, array(CalendarEventSchema).readonly(), { auth: "admin" })
29500
+ }
29501
+ };
29502
+ /**
29503
+ * `calendars` — system-scoped singleton. Public surface admin-ui and the
29504
+ * notification-center calendar-events producer consume.
29505
+ *
29506
+ * Walks every `calendar-source` provider, stamps each calendar with a
29507
+ * cluster-stable id (`<sourceAddonId>:<localId>`) and the providing
29508
+ * addon's id, and fans `listEvents` out the same way. Home Assistant
29509
+ * import is a built-in refresh of the HA source rather than a second
29510
+ * registry — `importFromHomeAssistant` asks that source to re-discover.
29511
+ */
29512
+ var CalendarEntrySchema = CalendarInfoSchema.extend({
29513
+ /** Cluster-stable id: `<providerAddonId>:<localId>`. */
29514
+ id: string().min(1),
29515
+ /** The addon that registered the calendar-source provider. */
29516
+ providerAddonId: string().min(1).optional()
29517
+ });
29518
+ var CalendarImportResultSchema = object({
29519
+ imported: number().int().nonnegative(),
29520
+ /** Absent when no HA calendar-source provider was reachable. */
29521
+ providerAddonId: string().min(1).optional(),
29522
+ error: string().optional()
29523
+ });
29524
+ method(_void(), array(CalendarEntrySchema).readonly(), { auth: "admin" }), method(CalendarListEventsInputSchema, array(CalendarEventSchema).readonly(), { auth: "admin" }), method(_void(), CalendarImportResultSchema, {
29525
+ auth: "admin",
29526
+ kind: "mutation"
29527
+ });
29528
+ /**
29399
29529
  * camera-credentials — device-scoped cap exposing the camera's network
29400
29530
  * + auth surface in a vendor-neutral shape.
29401
29531
  *
@@ -33944,38 +34074,52 @@ DeviceType.Camera, method(object({
33944
34074
  deviceId: number(),
33945
34075
  status: OsdStatusSchema
33946
34076
  });
34077
+ _enum([
34078
+ "value",
34079
+ "unit",
34080
+ "full"
34081
+ ]);
34082
+ _enum([
34083
+ "value+unit",
34084
+ "value",
34085
+ "unit"
34086
+ ]);
34087
+ /**
34088
+ * How the operator reached the leaf device. `deviceId` is ALWAYS the
34089
+ * OSD-supported leaf that is read; `containerId` is set when the pick
34090
+ * went through a container (gateway) first.
34091
+ */
34092
+ var OsdTemplatePickSchema = object({
34093
+ containerId: number().int().optional(),
34094
+ deviceId: number().int()
34095
+ });
34096
+ /**
34097
+ * One named binding inside a multi-device template: device path + unit
34098
+ * selection + precision. The parser expands `{alias}`, `{alias.value}`,
34099
+ * `{alias.unit}`, or `{alias.full}` from the same binding — there is no
34100
+ * per-binding format lock.
34101
+ */
34102
+ var OsdTemplateBindingSchema = object({
34103
+ /** Token stem in the parser, e.g. `t` → `{t}` / `{t.value}`. Unique within the template. */
34104
+ alias: string().min(1).max(32).regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "alias must start with a letter"),
34105
+ pick: OsdTemplatePickSchema,
34106
+ capName: string().min(1).max(64),
34107
+ valuePath: string().min(1).max(64),
34108
+ /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
34109
+ displayUnit: string().max(16).optional(),
34110
+ maxDecimals: number().int().min(0).max(4).default(1)
34111
+ });
33947
34112
  /**
33948
- * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
33949
- *
33950
- * The `osd` cap is the firmware contract: it probes a camera's overlay
33951
- * SLOTS and writes literal text into one. It has no idea WHERE that text
33952
- * comes from, and it must not — a driver that grew a "show the temperature
33953
- * here" feature would grow it once per vendor.
33954
- *
33955
- * This cap owns the other half: a per-(camera, slot) BINDING that says
33956
- * which value feeds the slot, how it is formatted, and under which
33957
- * conditions it is shown at all. One addon renders every binding on every
33958
- * camera, so a new source costs zero driver code.
33959
- *
33960
- * Three deliberate choices, each with a rejected alternative:
33961
- *
33962
- * 1. A source is `(capName, valuePath)` over the kernel's device
33963
- * runtime-state mirror — NOT a closed enum of source kinds. Every
33964
- * cap-keyed slice a device publishes is bindable the day the cap
33965
- * ships. The rejected alternative (one enum member per source, with
33966
- * a resolver branch each) is what makes "add the humidity too" a
33967
- * code change.
33968
- * 2. The display gate reuses `NcConditionsSchema` verbatim — the
33969
- * notification centre's condition vocabulary — rather than a parallel
33970
- * model. An operator who has learned one condition editor has learned
33971
- * both.
33972
- * 3. Because the renderer's facts are device STATE and not a detection
33973
- * record, only a SUBSET of that vocabulary can be answered here.
33974
- * `setSlotBinding` REJECTS the rest at write time (see
33975
- * `getConditionSupport`). It does not accept-then-fail-closed: a
33976
- * condition that can never be true renders a permanently blank
33977
- * overlay, and a blank overlay looks exactly like a broken camera.
34113
+ * A named template: bindings (alias → leaf field) + a parser that
34114
+ * references `{alias}` / `{alias.value}` / `{alias.unit}` / `{alias.full}`.
33978
34115
  */
34116
+ var OsdNamedTemplateSchema = object({
34117
+ id: string().min(1).max(64),
34118
+ name: string().min(1).max(64),
34119
+ bindings: array(OsdTemplateBindingSchema).max(32).default([]),
34120
+ /** Parser with alias placeholders, e.g. `{t.value} {t.unit} / {h}`. */
34121
+ parser: string().max(256).default("")
34122
+ });
33979
34123
  /** Where a slot's value comes from. */
33980
34124
  var OsdSourceSchema = discriminatedUnion("kind", [
33981
34125
  object({
@@ -33994,11 +34138,17 @@ var OsdSourceSchema = discriminatedUnion("kind", [
33994
34138
  deviceId: number().int().optional(),
33995
34139
  capName: string().min(1).max(64),
33996
34140
  /** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
33997
- valuePath: string().min(1).max(64)
34141
+ valuePath: string().min(1).max(64),
34142
+ /** Convert TO this unit at render. Absent = no conversion. */
34143
+ displayUnit: string().max(16).optional()
33998
34144
  }),
33999
34145
  object({
34000
34146
  kind: literal("latest-recognition"),
34001
34147
  recognition: _enum(["person", "plate"])
34148
+ }),
34149
+ object({
34150
+ kind: literal("template"),
34151
+ templateId: string().min(1).max(64)
34002
34152
  })
34003
34153
  ]);
34004
34154
  var OsdSlotBindingSchema = object({
@@ -34014,7 +34164,10 @@ var OsdSlotBindingSchema = object({
34014
34164
  * documented workaround for firmwares that reject `.` in overlay text.
34015
34165
  */
34016
34166
  maxDecimals: number().int().min(0).max(4).default(1),
34017
- /** Appended via `${unit}`. The state mirror does not carry units. */
34167
+ /**
34168
+ * Appended via `${unit}` when no unit conversion produces a label.
34169
+ * Prefer `source.displayUnit` for converted device-state values.
34170
+ */
34018
34171
  unitLabel: string().max(8).optional(),
34019
34172
  /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
34020
34173
  valueMap: record(string(), string()).optional(),
@@ -34123,6 +34276,12 @@ method(object({ deviceId: number().int() }), object({
34123
34276
  }), method(object({ deviceId: number().int() }), object({ results: array(OsdRenderResultSchema) }), {
34124
34277
  kind: "mutation",
34125
34278
  auth: "admin"
34279
+ }), method(object({}), object({ templates: array(OsdNamedTemplateSchema) }), { auth: "admin" }), method(object({ template: OsdNamedTemplateSchema }), object({ template: OsdNamedTemplateSchema }), {
34280
+ kind: "mutation",
34281
+ auth: "admin"
34282
+ }), method(object({ templateId: string().min(1).max(64) }), object({ success: literal(true) }), {
34283
+ kind: "mutation",
34284
+ auth: "admin"
34126
34285
  });
34127
34286
  /**
34128
34287
  * Feeder connectivity / power status — mirrors the HA petkit device-status
@@ -42917,6 +43076,36 @@ Object.freeze({
42917
43076
  addonId: null,
42918
43077
  access: "create"
42919
43078
  },
43079
+ "calendars.importFromHomeAssistant": {
43080
+ capName: "calendars",
43081
+ capScope: "system",
43082
+ addonId: null,
43083
+ access: "create"
43084
+ },
43085
+ "calendars.listCalendars": {
43086
+ capName: "calendars",
43087
+ capScope: "system",
43088
+ addonId: null,
43089
+ access: "view"
43090
+ },
43091
+ "calendars.listEvents": {
43092
+ capName: "calendars",
43093
+ capScope: "system",
43094
+ addonId: null,
43095
+ access: "view"
43096
+ },
43097
+ "calendarSource.listCalendars": {
43098
+ capName: "calendar-source",
43099
+ capScope: "system",
43100
+ addonId: null,
43101
+ access: "view"
43102
+ },
43103
+ "calendarSource.listEvents": {
43104
+ capName: "calendar-source",
43105
+ capScope: "system",
43106
+ addonId: null,
43107
+ access: "view"
43108
+ },
42920
43109
  "cameraCredentials.getCredentials": {
42921
43110
  capName: "camera-credentials",
42922
43111
  capScope: "device",
@@ -45911,6 +46100,12 @@ Object.freeze({
45911
46100
  addonId: null,
45912
46101
  access: "create"
45913
46102
  },
46103
+ "osdManager.deleteTemplate": {
46104
+ capName: "osd-manager",
46105
+ capScope: "system",
46106
+ addonId: null,
46107
+ access: "delete"
46108
+ },
45914
46109
  "osdManager.getConditionSupport": {
45915
46110
  capName: "osd-manager",
45916
46111
  capScope: "system",
@@ -45929,6 +46124,12 @@ Object.freeze({
45929
46124
  addonId: null,
45930
46125
  access: "view"
45931
46126
  },
46127
+ "osdManager.listTemplates": {
46128
+ capName: "osd-manager",
46129
+ capScope: "system",
46130
+ addonId: null,
46131
+ access: "view"
46132
+ },
45932
46133
  "osdManager.previewSlot": {
45933
46134
  capName: "osd-manager",
45934
46135
  capScope: "system",
@@ -45947,6 +46148,12 @@ Object.freeze({
45947
46148
  addonId: null,
45948
46149
  access: "create"
45949
46150
  },
46151
+ "osdManager.upsertTemplate": {
46152
+ capName: "osd-manager",
46153
+ capScope: "system",
46154
+ addonId: null,
46155
+ access: "create"
46156
+ },
45950
46157
  "petFeeder.callPet": {
45951
46158
  capName: "pet-feeder",
45952
46159
  capScope: "device",
@@ -53313,6 +53520,12 @@ Object.defineProperty(exports, "buttonCapability", {
53313
53520
  return buttonCapability;
53314
53521
  }
53315
53522
  });
53523
+ Object.defineProperty(exports, "calendarSourceCapability", {
53524
+ enumerable: true,
53525
+ get: function() {
53526
+ return calendarSourceCapability;
53527
+ }
53528
+ });
53316
53529
  Object.defineProperty(exports, "carbonMonoxideCapability", {
53317
53530
  enumerable: true,
53318
53531
  get: function() {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  //#endregion
6
- const require_dist = require("../dist-DMn4t1u_.js");
6
+ const require_dist = require("../dist-DLQgcqQT.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  //#region src/ha-export/topics.ts
9
9
  /**
@@ -1,4 +1,4 @@
1
- import { C as buildAddonRouteProvider, F as deviceExportCapability, Nt as EventCategory, Q as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, kt as string, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, xt as nodePin, yt as BaseAddon } from "../dist-evNLjXgk.mjs";
1
+ import { $ as oauthIntegrationCapability, At as string, C as buildAddonRouteProvider, I as deviceExportCapability, Pt as EventCategory, St as nodePin, a as COCO_TO_MACRO, bt as BaseAddon, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema } from "../dist-CvzpCuzm.mjs";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  //#region src/ha-export/topics.ts
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.153",
3
+ "version": "1.2.154",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",
@@ -82,6 +82,9 @@
82
82
  },
83
83
  {
84
84
  "name": "addon-routes"
85
+ },
86
+ {
87
+ "name": "calendar-source"
85
88
  }
86
89
  ]
87
90
  },