@camstack/addon-provider-homeassistant 1.2.649 → 1.2.651

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
- import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-D3FBA6uG.mjs";
1
+ import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-CCmZ9I6P.mjs";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  import "node:zlib";
4
4
  import "node:fs";
@@ -940,19 +940,22 @@ var NATIVE_CAP_PLATFORMS = {
940
940
  */
941
941
  cover: {
942
942
  platform: "cover",
943
+ availableWhenOnline: true,
943
944
  primary: { from: null },
944
945
  controls: {
945
946
  position: {
946
947
  from: "position",
947
948
  min: 0,
948
949
  max: 100,
949
- step: 1
950
+ step: 1,
951
+ requiresFeature: "cover-positionable"
950
952
  },
951
953
  tilt: {
952
954
  from: "tilt",
953
955
  min: 0,
954
956
  max: 100,
955
- step: 1
957
+ step: 1,
958
+ requiresFeature: "cover-tilt"
956
959
  },
957
960
  open: {
958
961
  from: "open",
@@ -964,7 +967,8 @@ var NATIVE_CAP_PLATFORMS = {
964
967
  },
965
968
  stop: {
966
969
  from: "stop",
967
- commandOnly: true
970
+ commandOnly: true,
971
+ requiresFeature: "cover-stoppable"
968
972
  }
969
973
  }
970
974
  },
@@ -1353,9 +1357,11 @@ function buildNativeComponent(input) {
1353
1357
  if (native === void 0) return null;
1354
1358
  const primary = resolveBinding(input, native.primary);
1355
1359
  if (primary === null) return null;
1360
+ const features = input.features ?? [];
1356
1361
  const absorbed = new Set([derivedEntityId(input.capName), ...primary.entities]);
1357
1362
  const controls = {};
1358
1363
  for (const [name, binding] of Object.entries(native.controls ?? {})) {
1364
+ if (binding.requiresFeature !== void 0 && !features.includes(binding.requiresFeature)) continue;
1359
1365
  const resolved = resolveBinding(input, binding);
1360
1366
  if (resolved === null) continue;
1361
1367
  controls[name] = resolved.control;
@@ -1374,6 +1380,11 @@ function buildNativeComponent(input) {
1374
1380
  ...primary.control,
1375
1381
  ...native.deviceClass !== void 0 ? { device_class: native.deviceClass } : {},
1376
1382
  ...native.icon !== void 0 ? { icon: native.icon } : {},
1383
+ ...native.availableWhenOnline === true ? {
1384
+ availability_topic: stateTopic(input.deviceKey, "online"),
1385
+ payload_available: "true",
1386
+ payload_not_available: "false"
1387
+ } : {},
1377
1388
  ...Object.keys(controls).length > 0 ? { controls } : {},
1378
1389
  ...native.constants,
1379
1390
  ...flags
@@ -2308,7 +2319,8 @@ var CAP_ENTITY_MAP = {
2308
2319
  unit: "%",
2309
2320
  icon: "mdi:arrow-up-down",
2310
2321
  writable: true,
2311
- range: PERCENT_RANGE
2322
+ range: PERCENT_RANGE,
2323
+ requiresFeature: "cover-positionable"
2312
2324
  },
2313
2325
  tilt: {
2314
2326
  platform: "number",
@@ -2318,7 +2330,8 @@ var CAP_ENTITY_MAP = {
2318
2330
  icon: "mdi:angle-acute",
2319
2331
  writable: true,
2320
2332
  range: PERCENT_RANGE,
2321
- enabledByDefault: false
2333
+ enabledByDefault: false,
2334
+ requiresFeature: "cover-tilt"
2322
2335
  }
2323
2336
  },
2324
2337
  commands: {
@@ -2333,7 +2346,8 @@ var CAP_ENTITY_MAP = {
2333
2346
  stop: {
2334
2347
  label: "Stop",
2335
2348
  icon: "mdi:stop",
2336
- enabledByDefault: false
2349
+ enabledByDefault: false,
2350
+ requiresFeature: "cover-stoppable"
2337
2351
  }
2338
2352
  }
2339
2353
  },
@@ -3340,7 +3354,8 @@ function capEntities(capName) {
3340
3354
  for (const [suffix, extra] of Object.entries(mapping.extras ?? {})) refs.push({
3341
3355
  entity: derivedExtraEntityId(capName, suffix),
3342
3356
  label: extra.label,
3343
- descriptor: extra
3357
+ descriptor: extra,
3358
+ ...extra.requiresFeature !== void 0 ? { requiresFeature: extra.requiresFeature } : {}
3344
3359
  });
3345
3360
  return refs;
3346
3361
  }
@@ -3359,8 +3374,9 @@ function deriveCapValue(derivation, raw) {
3359
3374
  return derivation.kind === "equals" ? raw === derivation.value : raw !== derivation.value;
3360
3375
  }
3361
3376
  /** A capability's entities as the catalog builders consume them. */
3362
- function capEntitySpecs(capName) {
3363
- const specs = capEntities(capName).map((ref) => ({
3377
+ function capEntitySpecs(capName, features = []) {
3378
+ const declared = (required) => required === void 0 || features.includes(required);
3379
+ const specs = capEntities(capName).filter((ref) => declared(ref.requiresFeature)).map((ref) => ({
3364
3380
  entity: ref.entity,
3365
3381
  platform: ref.descriptor.platform,
3366
3382
  label: ref.label,
@@ -3373,15 +3389,18 @@ function capEntitySpecs(capName) {
3373
3389
  ...ref.descriptor.entityCategory !== void 0 ? { entityCategory: ref.descriptor.entityCategory } : {},
3374
3390
  ...ref.descriptor.writable === true ? { writable: true } : {}
3375
3391
  }));
3376
- for (const ref of capCommandEntities(capName)) specs.push({
3377
- entity: ref.entity,
3378
- platform: "button",
3379
- label: ref.label,
3380
- enabledByDefault: ref.command.enabledByDefault ?? true,
3381
- writable: true,
3382
- ...ref.command.icon !== void 0 ? { icon: ref.command.icon } : {},
3383
- ...ref.command.entityCategory !== void 0 ? { entityCategory: ref.command.entityCategory } : {}
3384
- });
3392
+ for (const ref of capCommandEntities(capName)) {
3393
+ if (!declared(ref.command.requiresFeature)) continue;
3394
+ specs.push({
3395
+ entity: ref.entity,
3396
+ platform: "button",
3397
+ label: ref.label,
3398
+ enabledByDefault: ref.command.enabledByDefault ?? true,
3399
+ writable: true,
3400
+ ...ref.command.icon !== void 0 ? { icon: ref.command.icon } : {},
3401
+ ...ref.command.entityCategory !== void 0 ? { entityCategory: ref.command.entityCategory } : {}
3402
+ });
3403
+ }
3385
3404
  return specs;
3386
3405
  }
3387
3406
  function readControlOptions(slice) {
@@ -3759,11 +3778,12 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
3759
3778
  deviceId: device.id,
3760
3779
  capName: cap,
3761
3780
  label: humanise(cap),
3781
+ features: device.features,
3762
3782
  slice: device.capSlices?.[cap],
3763
3783
  mapping
3764
3784
  });
3765
3785
  if (plan === null) {
3766
- specs.push(...capEntitySpecs(cap).map((spec) => degradeUnsupported(spec, options)));
3786
+ specs.push(...capEntitySpecs(cap, device.features).map((spec) => degradeUnsupported(spec, options)));
3767
3787
  continue;
3768
3788
  }
3769
3789
  native[toComponentKey(plan.component.platform, derivedEntityId(cap))] = plan.component;
@@ -3774,7 +3794,7 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
3774
3794
  * to make the device look tidy would be a silent loss of a value an
3775
3795
  * operator already automates on.
3776
3796
  */
3777
- specs.push(...capEntitySpecs(cap).filter((spec) => !plan.absorbed.has(spec.entity)));
3797
+ specs.push(...capEntitySpecs(cap, device.features).filter((spec) => !plan.absorbed.has(spec.entity)));
3778
3798
  }
3779
3799
  const assembled = assemble(device, specs);
3780
3800
  return {
@@ -4636,6 +4656,15 @@ function isCameraCommand(command) {
4636
4656
  function isCapCommand(command) {
4637
4657
  return !isCameraCommand(command);
4638
4658
  }
4659
+ var APPLIED = { ok: true };
4660
+ /** The 422 body of a command that was not applied: the reason travels with it. */
4661
+ function commandNotAppliedBody(refusal) {
4662
+ return {
4663
+ error: "command not applied",
4664
+ reason: refusal.reason,
4665
+ ...refusal.message !== void 0 ? { message: refusal.message } : {}
4666
+ };
4667
+ }
4639
4668
  /**
4640
4669
  * Make the call the command names.
4641
4670
  *
@@ -5427,6 +5456,10 @@ var PushClient = class {
5427
5456
  disposed = false;
5428
5457
  lastFailure = null;
5429
5458
  suspendedUntil = 0;
5459
+ /** When the current unbroken streak of 404s began; 0 when there is none. */
5460
+ first404At = 0;
5461
+ /** A suspension was entered and not yet logged as left. */
5462
+ suspensionLogged = false;
5430
5463
  constructor(options) {
5431
5464
  this.transport = options.transport;
5432
5465
  this.logger = options.logger;
@@ -5445,9 +5478,20 @@ var PushClient = class {
5445
5478
  get suspended() {
5446
5479
  return this.suspendedUntil > Date.now();
5447
5480
  }
5481
+ /** Epoch ms of the next probe while suspended; 0 otherwise. */
5482
+ get recheckAt() {
5483
+ return this.suspended ? this.suspendedUntil : 0;
5484
+ }
5448
5485
  /** Probe again at once — the operator changed something. */
5449
5486
  resetBackoff() {
5450
5487
  this.suspendedUntil = 0;
5488
+ this.first404At = 0;
5489
+ this.logSuspensionEnded("reset by a settings change");
5490
+ }
5491
+ logSuspensionEnded(why) {
5492
+ if (!this.suspensionLogged) return;
5493
+ this.suspensionLogged = false;
5494
+ this.logger.info(`ha-export: push link no longer suspended (${why}), probing again`, { meta: { brokerId: this.brokerId } });
5451
5495
  }
5452
5496
  /** Messages discarded because HA did not answer. Never silent. */
5453
5497
  get droppedCount() {
@@ -5528,8 +5572,13 @@ var PushClient = class {
5528
5572
  this.dropped += message.type === "batch" ? message.items.length : 1;
5529
5573
  return false;
5530
5574
  }
5575
+ if (this.suspendedUntil !== 0 && !this.suspended) {
5576
+ this.suspendedUntil = 0;
5577
+ this.logSuspensionEnded("recheck time reached");
5578
+ }
5531
5579
  try {
5532
5580
  await this.transport(message);
5581
+ this.first404At = 0;
5533
5582
  this.onReachable();
5534
5583
  return true;
5535
5584
  } catch (err) {
@@ -5556,6 +5605,7 @@ var PushClient = class {
5556
5605
  this.available = true;
5557
5606
  this.lastFailure = null;
5558
5607
  this.suspendedUntil = 0;
5608
+ this.logSuspensionEnded("Home Assistant answered");
5559
5609
  /**
5560
5610
  * The link came back. Drop the dedup cache so the reconcile that
5561
5611
  * follows re-pushes every value: HA may have restarted and lost its
@@ -5593,11 +5643,29 @@ var PushClient = class {
5593
5643
  */
5594
5644
  onUnreachable(message, err) {
5595
5645
  this.available = false;
5596
- const failure = describePushFailure(err);
5646
+ const now = Date.now();
5647
+ const described = describePushFailure(err);
5648
+ let failure = described;
5649
+ if (described.kind === "component-missing") {
5650
+ if (this.first404At === 0) this.first404At = now;
5651
+ if (now - this.first404At < 6e4) failure = {
5652
+ kind: "other",
5653
+ permanent: false,
5654
+ text: "HTTP 404 — Home Assistant has not registered the CamStack endpoint (yet); retrying"
5655
+ };
5656
+ } else this.first404At = 0;
5597
5657
  this.lastFailure = failure;
5598
- if (failure.permanent) this.suspendedUntil = Date.now() + COMPONENT_RECHECK_MS;
5599
5658
  this.dropped += message.type === "batch" ? message.items.length : 1;
5600
- const now = Date.now();
5659
+ if (failure.permanent) {
5660
+ this.suspendedUntil = now + COMPONENT_RECHECK_MS;
5661
+ this.suspensionLogged = true;
5662
+ this.logger.warn(`ha-export: push link SUSPENDED (${failure.text}); next probe at ${new Date(this.suspendedUntil).toISOString()}`, { meta: {
5663
+ brokerId: this.brokerId,
5664
+ failureKind: failure.kind,
5665
+ recheckAt: this.suspendedUntil,
5666
+ recheckInMs: COMPONENT_RECHECK_MS
5667
+ } });
5668
+ }
5601
5669
  if (now - this.droppedLoggedAt < DROP_LOG_INTERVAL_MS) return;
5602
5670
  this.droppedLoggedAt = now;
5603
5671
  this.logger.warn(`ha-export: push to Home Assistant failed (${failure.text}), messages dropped`, { meta: {
@@ -5609,6 +5677,19 @@ var PushClient = class {
5609
5677
  } });
5610
5678
  }
5611
5679
  };
5680
+ //#endregion
5681
+ //#region src/ha-export/link-gaps.ts
5682
+ /** A parked link is reported, never silently excluded. */
5683
+ function summariseLinkGaps(links) {
5684
+ return {
5685
+ failing: links.filter((l) => !l.healthy && !l.suspended).map((l) => l.brokerId),
5686
+ suspended: links.filter((l) => l.suspended).map((l) => ({
5687
+ brokerId: l.brokerId,
5688
+ recheckAt: l.recheckAt
5689
+ })),
5690
+ anyLive: links.some((l) => !l.suspended)
5691
+ };
5692
+ }
5612
5693
  var ReachabilityReconciler = class {
5613
5694
  options;
5614
5695
  minIntervalMs;
@@ -8080,13 +8161,21 @@ var HaExportAddon = class extends BaseAddon {
8080
8161
  const delivered = await this.announce(exported, fullResync);
8081
8162
  await this.pushFullState(exported, snapshots);
8082
8163
  if (synthetic !== null) await this.pushSynthetic(projectSynthetic(synthetic, Date.now()), [...enabled]);
8083
- const failing = [...this.links.values()].filter((link) => !link.client.healthy && !link.client.suspended);
8084
- const anyLive = [...this.links.values()].some((link) => !link.client.suspended);
8085
- if (failing.length > 0 || !delivered && anyLive) {
8164
+ const gaps = summariseLinkGaps([...this.links.entries()].map(([brokerId, link]) => ({
8165
+ brokerId,
8166
+ healthy: link.client.healthy,
8167
+ suspended: link.client.suspended,
8168
+ recheckAt: link.client.recheckAt
8169
+ })));
8170
+ if (gaps.failing.length > 0 || !delivered && gaps.anyLive) {
8086
8171
  this.resync.markGap("push-not-delivered");
8087
8172
  this.lastStructureFingerprint = void 0;
8088
8173
  this.ctx.logger.warn("ha-export: a push was not delivered, the next pass is a full resync");
8089
8174
  }
8175
+ if (gaps.suspended.length > 0) this.ctx.logger.warn("ha-export: push links suspended, Home Assistant receives nothing", { meta: { suspended: gaps.suspended.map((s) => ({
8176
+ brokerId: s.brokerId,
8177
+ recheckAt: new Date(s.recheckAt).toISOString()
8178
+ })) } });
8090
8179
  this.ctx.logger.info("ha-export: reconciled", { meta: {
8091
8180
  reason,
8092
8181
  fullResync,
@@ -8094,6 +8183,7 @@ var HaExportAddon = class extends BaseAddon {
8094
8183
  devices: exported.size,
8095
8184
  entities: entityCount,
8096
8185
  unclassifiedCaps: this.unclassified.length,
8186
+ suspendedLinks: gaps.suspended.length,
8097
8187
  durationMs: Date.now() - startedAt
8098
8188
  } });
8099
8189
  if (this.unclassified.length > 0) this.ctx.logger.warn("ha-export: capabilities in neither the map nor the exclusion list", { meta: { caps: this.unclassified } });
@@ -9133,9 +9223,23 @@ var HaExportAddon = class extends BaseAddon {
9133
9223
  reply.send({ error: `unroutable command (${resolution.reason})` });
9134
9224
  return;
9135
9225
  }
9136
- const applied = await this.dispatch(resolution.command);
9137
- reply.status(applied ? 200 : 422);
9138
- reply.send(applied ? {} : { error: "command not applied" });
9226
+ const outcome = await this.dispatch(resolution.command);
9227
+ if (outcome.ok) {
9228
+ reply.status(200);
9229
+ reply.send({});
9230
+ return;
9231
+ }
9232
+ this.ctx.logger.warn("ha-export: command answered 422", {
9233
+ tags: { deviceId: exported.deviceId },
9234
+ meta: {
9235
+ topic,
9236
+ entity: parsed.entity,
9237
+ reason: outcome.reason,
9238
+ message: outcome.message
9239
+ }
9240
+ });
9241
+ reply.status(422);
9242
+ reply.send(commandNotAppliedBody(outcome));
9139
9243
  }
9140
9244
  async dispatch(command) {
9141
9245
  try {
@@ -9149,7 +9253,7 @@ var HaExportAddon = class extends BaseAddon {
9149
9253
  */
9150
9254
  if (isCapCommand(command)) {
9151
9255
  const outcome = await applyCapCommand(await this.ctx.fetchDevice(command.deviceId), command);
9152
- if (outcome.ok) return true;
9256
+ if (outcome.ok) return APPLIED;
9153
9257
  if (outcome.reason === "no-provider") return this.noProvider(command.deviceId, outcome.capName);
9154
9258
  this.ctx.logger.warn("ha-export: the device refused the command", {
9155
9259
  tags: { deviceId: command.deviceId },
@@ -9159,7 +9263,11 @@ var HaExportAddon = class extends BaseAddon {
9159
9263
  ...outcome.error !== void 0 ? { error: outcome.error } : {}
9160
9264
  }
9161
9265
  });
9162
- return false;
9266
+ return {
9267
+ ok: false,
9268
+ reason: outcome.reason,
9269
+ ...outcome.error !== void 0 ? { message: outcome.error } : {}
9270
+ };
9163
9271
  }
9164
9272
  switch (command.kind) {
9165
9273
  case "camera-function": {
@@ -9169,7 +9277,11 @@ var HaExportAddon = class extends BaseAddon {
9169
9277
  tags: { deviceId: command.deviceId },
9170
9278
  meta: { switchId: command.switchId }
9171
9279
  });
9172
- return false;
9280
+ return {
9281
+ ok: false,
9282
+ reason: "unknown-function",
9283
+ message: "the switch id is not a camera function"
9284
+ };
9173
9285
  }
9174
9286
  /**
9175
9287
  * A FRESH read for this one device — the pass-level caches are up to
@@ -9179,7 +9291,11 @@ var HaExportAddon = class extends BaseAddon {
9179
9291
  const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId: command.deviceId });
9180
9292
  if (device === null) {
9181
9293
  this.ctx.logger.warn("ha-export: dropping a camera-function command, the device is not in the registry", { tags: { deviceId: command.deviceId } });
9182
- return false;
9294
+ return {
9295
+ ok: false,
9296
+ reason: "device-not-found",
9297
+ message: "the device is not in the registry"
9298
+ };
9183
9299
  }
9184
9300
  const privacySlice = await this.ctx.api.deviceState.getCapSlice.query({
9185
9301
  deviceId: command.deviceId,
@@ -9215,22 +9331,26 @@ var HaExportAddon = class extends BaseAddon {
9215
9331
  tags: { deviceId: command.deviceId },
9216
9332
  meta: { switchId: parsed.data }
9217
9333
  });
9218
- return false;
9334
+ return {
9335
+ ok: false,
9336
+ reason: "function-unavailable",
9337
+ message: "the camera function is not available"
9338
+ };
9219
9339
  }
9220
9340
  await applyCameraFunctionWrite(this.ctx.api, command.deviceId, write, sourceNodeId);
9221
- return true;
9341
+ return APPLIED;
9222
9342
  }
9223
9343
  case "reboot": {
9224
9344
  const device = await this.ctx.fetchDevice(command.deviceId);
9225
9345
  if (device.reboot === void 0) return this.noProvider(command.deviceId, "reboot");
9226
9346
  await device.reboot.reboot({});
9227
- return true;
9347
+ return APPLIED;
9228
9348
  }
9229
9349
  case "ptz-move": {
9230
9350
  const device = await this.ctx.fetchDevice(command.deviceId);
9231
9351
  if (device.ptz === void 0) return this.noProvider(command.deviceId, "ptz");
9232
9352
  await device.ptz.move(PTZ_MOVE_BY_DIRECTION[command.direction] ?? {});
9233
- return true;
9353
+ return APPLIED;
9234
9354
  }
9235
9355
  case "ptz-preset": {
9236
9356
  const device = await this.ctx.fetchDevice(command.deviceId);
@@ -9241,10 +9361,14 @@ var HaExportAddon = class extends BaseAddon {
9241
9361
  tags: { deviceId: command.deviceId },
9242
9362
  meta: { preset: command.preset }
9243
9363
  });
9244
- return false;
9364
+ return {
9365
+ ok: false,
9366
+ reason: "no-such-preset",
9367
+ message: "no preset by that name"
9368
+ };
9245
9369
  }
9246
9370
  await device.ptz.goToPreset({ presetId: preset.id });
9247
- return true;
9371
+ return APPLIED;
9248
9372
  }
9249
9373
  }
9250
9374
  } catch (err) {
@@ -9260,7 +9384,11 @@ var HaExportAddon = class extends BaseAddon {
9260
9384
  error: errMsg(err)
9261
9385
  }
9262
9386
  });
9263
- return false;
9387
+ return {
9388
+ ok: false,
9389
+ reason: "error",
9390
+ message: errMsg(err)
9391
+ };
9264
9392
  }
9265
9393
  }
9266
9394
  /** A cap the device does not provide. Refused, and never silently. */
@@ -9269,7 +9397,11 @@ var HaExportAddon = class extends BaseAddon {
9269
9397
  tags: { deviceId },
9270
9398
  meta: { capName }
9271
9399
  });
9272
- return false;
9400
+ return {
9401
+ ok: false,
9402
+ reason: "no-provider",
9403
+ message: `the device has no provider for ${capName}`
9404
+ };
9273
9405
  }
9274
9406
  /**
9275
9407
  * A public, signed, expiring route Home Assistant can fetch bytes from.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.649",
3
+ "version": "1.2.651",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",
@@ -127,7 +127,7 @@
127
127
  "description": "Device provider (import) + native entity export (push). Two addons, one bundle, separate processes."
128
128
  },
129
129
  "requires": {
130
- "server": ">=1.2.738"
130
+ "server": ">=1.2.741"
131
131
  }
132
132
  },
133
133
  "files": [