ha-mcp-dev 8.3.0.dev2379__tar.gz → 8.3.0.dev2385__tar.gz

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.
Files changed (276) hide show
  1. {ha_mcp_dev-8.3.0.dev2379/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2385}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/cs.json +1 -1
  4. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/de.json +1 -1
  5. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/eo.json +1 -1
  6. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/es.json +1 -1
  7. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/fr.json +1 -1
  8. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/it.json +1 -1
  9. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/nl.json +1 -1
  10. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/pl.json +1 -1
  11. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/ru.json +1 -1
  12. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/sv.json +1 -1
  13. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/zh-Hans.json +1 -1
  14. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/device_control.py +166 -109
  15. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/_base.py +6 -1
  16. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/_entities.py +22 -10
  17. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_search.py +10 -7
  18. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_service.py +26 -6
  19. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  20. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/LICENSE +0 -0
  21. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/MANIFEST.in +0 -0
  22. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/README.md +0 -0
  23. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/setup.cfg +0 -0
  24. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/__init__.py +0 -0
  25. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/__main__.py +0 -0
  26. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_pypi_marker +0 -0
  27. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_rich_compat.py +0 -0
  28. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/__init__.py +0 -0
  29. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/requirements.txt +0 -0
  30. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  31. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  32. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  33. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  34. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  35. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  36. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  37. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  38. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  39. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  40. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  41. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  42. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  43. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  44. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  45. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  46. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  47. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  48. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  49. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  50. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  51. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  52. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  53. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  54. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  55. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  56. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  57. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  58. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  59. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  60. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  61. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  62. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  63. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  64. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  65. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  66. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  67. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  68. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  69. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  70. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  71. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  72. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  73. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  74. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  75. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  76. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  77. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  78. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  79. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  80. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  81. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  82. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  83. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  84. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  85. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  86. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/_version.py +0 -0
  87. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/auth/__init__.py +0 -0
  88. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/auth/consent_form.py +0 -0
  89. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/auth/oidc_compat.py +0 -0
  90. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/auth/provider.py +0 -0
  91. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/backup_manager.py +0 -0
  92. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/browser_landing.py +0 -0
  93. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/client/__init__.py +0 -0
  94. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/client/rest_client.py +0 -0
  95. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/client/supervisor_client.py +0 -0
  96. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/client/websocket_client.py +0 -0
  97. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/client/websocket_listener.py +0 -0
  98. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/config.py +0 -0
  99. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  100. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  101. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  102. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  103. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  104. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  105. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/errors.py +0 -0
  106. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  107. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/llm_exposure.py +0 -0
  108. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/__init__.py +0 -0
  109. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/approval_queue.py +0 -0
  110. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/editing.py +0 -0
  111. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/evaluator.py +0 -0
  112. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/handlers.py +0 -0
  113. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/middleware.py +0 -0
  114. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/model.py +0 -0
  115. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/persistence.py +0 -0
  116. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/policy/value_sources.py +0 -0
  117. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/py.typed +0 -0
  118. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/read_only.py +0 -0
  119. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/redaction.py +0 -0
  120. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/renamed_tools.py +0 -0
  121. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  122. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  123. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  124. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  125. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  126. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  127. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  128. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  129. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  130. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  131. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  132. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  133. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  134. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  135. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  136. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  137. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  138. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  139. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  140. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  141. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  142. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  143. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  144. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  145. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  146. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/server.py +0 -0
  147. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/__init__.py +0 -0
  148. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  149. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  150. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  151. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  152. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  153. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  154. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  155. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  156. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  157. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_theme.py +0 -0
  158. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  159. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  160. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  161. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/settings.css +0 -0
  162. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/settings.html +0 -0
  163. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/settings_ui/settings.js +0 -0
  164. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/smoke_test.py +0 -0
  165. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  166. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/strict_bps.py +0 -0
  167. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/__init__.py +0 -0
  168. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/auto_backup.py +0 -0
  169. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/backup.py +0 -0
  170. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  171. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/best_practice_result.py +0 -0
  172. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  173. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/component_api.py +0 -0
  174. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/component_config_entries.py +0 -0
  175. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/component_config_reads.py +0 -0
  176. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/component_devices.py +0 -0
  177. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/component_registries.py +0 -0
  178. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  179. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  180. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  181. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  182. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  183. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  184. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  185. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  186. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  187. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/hacs_registration.py +0 -0
  188. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/helpers.py +0 -0
  189. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  190. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/radio/__init__.py +0 -0
  191. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/radio/base.py +0 -0
  192. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/radio/matter.py +0 -0
  193. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/radio/thread.py +0 -0
  194. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  195. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/radio/zwave.py +0 -0
  196. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/reference_validator.py +0 -0
  197. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/registry.py +0 -0
  198. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  199. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  200. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  201. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  202. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  203. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  204. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  205. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  206. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  207. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_addons.py +0 -0
  208. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_areas.py +0 -0
  209. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  210. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  211. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  212. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_calendar.py +0 -0
  213. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_camera.py +0 -0
  214. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_categories.py +0 -0
  215. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_code.py +0 -0
  216. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  217. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  218. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  219. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  220. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  221. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  222. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_dev.py +0 -0
  223. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_energy.py +0 -0
  224. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_entities.py +0 -0
  225. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  226. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_groups.py +0 -0
  227. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_hacs.py +0 -0
  228. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_history.py +0 -0
  229. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_integrations.py +0 -0
  230. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_labels.py +0 -0
  231. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_radio.py +0 -0
  232. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_registry.py +0 -0
  233. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_resources.py +0 -0
  234. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  235. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_services.py +0 -0
  236. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_system.py +0 -0
  237. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_themes.py +0 -0
  238. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_todo.py +0 -0
  239. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_traces.py +0 -0
  240. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_updates.py +0 -0
  241. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_utility.py +0 -0
  242. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  243. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  244. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  245. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/tools_zones.py +0 -0
  246. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/util_helpers.py +0 -0
  247. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/tools/validation_middleware.py +0 -0
  248. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/transforms/__init__.py +0 -0
  249. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/transforms/categorized_search.py +0 -0
  250. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  251. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/transport_security.py +0 -0
  252. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/update_check.py +0 -0
  253. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/__init__.py +0 -0
  254. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/config_hash.py +0 -0
  255. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/config_write_lock.py +0 -0
  256. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/data_paths.py +0 -0
  257. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/domain_handlers.py +0 -0
  258. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  259. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  260. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/operation_manager.py +0 -0
  261. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/python_sandbox.py +0 -0
  262. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/skill_loader.py +0 -0
  263. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/utils/usage_logger.py +0 -0
  264. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/visibility/__init__.py +0 -0
  265. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/visibility/enforcement.py +0 -0
  266. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/visibility/model.py +0 -0
  267. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/visibility/persistence.py +0 -0
  268. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp/visibility/resolver.py +0 -0
  269. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  270. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  271. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  272. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  273. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  274. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/tests/__init__.py +0 -0
  275. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/tests/test_constants.py +0 -0
  276. {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2385}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 8.3.0.dev2379
3
+ Version: 8.3.0.dev2385
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "8.3.0.dev2379"
7
+ version = "8.3.0.dev2385"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  # Upper bound widened to <3.15 so the package installs on Home Assistant's
@@ -470,7 +470,7 @@
470
470
  "tools": {
471
471
  "ha_bulk_control": {
472
472
  "title": "Hromadné ovládání",
473
- "description": "Ovládejte více zařízení s podporou hromadných operací a sledováním přes WebSocket."
473
+ "description": "Správa více akcí entit v jednom požadavku."
474
474
  },
475
475
  "ha_call_event": {
476
476
  "title": "Vyvolat událost",
@@ -501,7 +501,7 @@
501
501
  "tools": {
502
502
  "ha_bulk_control": {
503
503
  "title": "Massensteuerung",
504
- "description": "Steuere mehrere Geräte mit Unterstützung für Massenoperationen und WebSocket-Verfolgung."
504
+ "description": "Verwalte mehrere Entitätsaktionen in einer Anfrage."
505
505
  },
506
506
  "ha_call_event": {
507
507
  "title": "Ereignis aufrufen",
@@ -470,7 +470,7 @@
470
470
  "tools": {
471
471
  "ha_bulk_control": {
472
472
  "title": "Amasa Kontrolo",
473
- "description": "Kontrolu plurajn aparatojn kun subteno por amasa operacio kaj WebSocket-spurado."
473
+ "description": "Mastrumi plurajn entajn agojn en unu peto."
474
474
  },
475
475
  "ha_call_event": {
476
476
  "title": "Voki Eventon",
@@ -501,7 +501,7 @@
501
501
  "tools": {
502
502
  "ha_bulk_control": {
503
503
  "title": "Control en bloque",
504
- "description": "Controla varios dispositivos a la vez con seguimiento por WebSocket."
504
+ "description": "Gestiona múltiples acciones de entidades en una sola solicitud."
505
505
  },
506
506
  "ha_call_event": {
507
507
  "title": "Lanzar evento",
@@ -501,7 +501,7 @@
501
501
  "tools": {
502
502
  "ha_bulk_control": {
503
503
  "title": "Contrôle groupé",
504
- "description": "Contrôler plusieurs appareils avec prise en charge des opérations en masse et suivi WebSocket."
504
+ "description": "Gérer plusieurs actions d'entité en une seule requête."
505
505
  },
506
506
  "ha_call_event": {
507
507
  "title": "Déclencher un événement",
@@ -501,7 +501,7 @@
501
501
  "tools": {
502
502
  "ha_bulk_control": {
503
503
  "title": "Controllo in blocco",
504
- "description": "Controlla più dispositivi con supporto alle operazioni in blocco e monitoraggio via WebSocket."
504
+ "description": "Gestisci più azioni di entità in una singola richiesta."
505
505
  },
506
506
  "ha_call_event": {
507
507
  "title": "Invia un evento",
@@ -470,7 +470,7 @@
470
470
  "tools": {
471
471
  "ha_bulk_control": {
472
472
  "title": "Bulkbediening",
473
- "description": "Bedien meerdere apparaten met ondersteuning voor bulkbewerkingen en WebSocket-tracking."
473
+ "description": "Beheer meerdere entiteitsacties in één verzoek."
474
474
  },
475
475
  "ha_call_event": {
476
476
  "title": "Roep Gebeurtenis aan",
@@ -470,7 +470,7 @@
470
470
  "tools": {
471
471
  "ha_bulk_control": {
472
472
  "title": "Sterowanie zbiorcze",
473
- "description": "Steruj wieloma urządzeniami z obsługą operacji zbiorczych i śledzeniem WebSocket."
473
+ "description": "Zarządzaj wieloma akcjami encji w jednym żądaniu."
474
474
  },
475
475
  "ha_call_event": {
476
476
  "title": "Wywołaj zdarzenie",
@@ -501,7 +501,7 @@
501
501
  "tools": {
502
502
  "ha_bulk_control": {
503
503
  "title": "Массовое управление",
504
- "description": "Управление несколькими устройствами с поддержкой массовых операций и отслеживанием через WebSocket."
504
+ "description": "Управление действиями нескольких сущностей в одном запросе."
505
505
  },
506
506
  "ha_call_event": {
507
507
  "title": "Вызвать событие",
@@ -470,7 +470,7 @@
470
470
  "tools": {
471
471
  "ha_bulk_control": {
472
472
  "title": "Masskontroll",
473
- "description": "Kontrollera flera enheter med stöd för massoperationer och WebSocket-spårning."
473
+ "description": "Hantera flera entitetsåtgärder i en enda begäran."
474
474
  },
475
475
  "ha_call_event": {
476
476
  "title": "Anropa Händelse",
@@ -501,7 +501,7 @@
501
501
  "tools": {
502
502
  "ha_bulk_control": {
503
503
  "title": "批量控制",
504
- "description": "支持批量操作与 WebSocket 跟踪,同时控制多个设备。"
504
+ "description": "在单个请求中管理多个实体操作。"
505
505
  },
506
506
  "ha_call_event": {
507
507
  "title": "调用事件",
@@ -42,10 +42,23 @@ from .helpers import (
42
42
  safe_info,
43
43
  safe_progress,
44
44
  )
45
+ from .tools_service import BulkControlOperation
45
46
  from .util_helpers import _SERVICE_TO_STATE
46
47
 
47
48
  logger = logging.getLogger(__name__)
48
49
 
50
+ # The bulk row contract is declared once, on the advertised transport schema.
51
+ # Reading its keys back here keeps the runtime batch validator from drifting
52
+ # out of sync with what models are told they may send.
53
+ _BULK_OPERATION_KEYS: frozenset[str] = frozenset(BulkControlOperation.__annotations__)
54
+
55
+ # The row shape models get wrong most often is a service-style row, so this
56
+ # leads every suggestion list a rejected bulk batch produces.
57
+ _BULK_ROW_SHAPE_SUGGESTION = (
58
+ "Use entity_id and action (e.g. action='off', not service='turn_off'); "
59
+ "send no domain or service keys and put action data in parameters"
60
+ )
61
+
49
62
  # The ha_mcp_tools/bulk_call_service WS command (Phase 3, D5a): the BATCH write
50
63
  # capability. When the component advertises ``bulk_call_service`` the consumer
51
64
  # resolves every op's domain/service server-side (D6), sends one register-before-fire
@@ -347,6 +360,10 @@ class DeviceControlTools:
347
360
  "set": "set_temperature",
348
361
  }
349
362
 
363
+ # Silent allowlist: _add_domain_params copies only these keys into the
364
+ # service call and drops everything else without an error, and a domain
365
+ # absent from this table loses every parameter. Extend it when adding
366
+ # support for a new parameter, or the parameter vanishes at dispatch.
350
367
  _DOMAIN_PARAMS: ClassVar[dict[str, list[str]]] = {
351
368
  "light": [
352
369
  "brightness",
@@ -474,10 +491,10 @@ class DeviceControlTools:
474
491
  """Check status of a device operation, waiting up to ``timeout_seconds`` for completion.
475
492
 
476
493
  Polls the in-memory operation registry (mutated by the WebSocket
477
- listener as state changes arrive) every 0.2s while the operation is
478
- pending, up to ``timeout_seconds``. Returns the final structured status
479
- completed/failed/timeout/pending produced by
480
- ``control_device_smart``.
494
+ listener as state changes arrive) while the operation is pending, at
495
+ 0.2s intervals except for a final shorter poll that lands exactly on
496
+ the deadline. Returns the final structured status —
497
+ completed/failed/timeout/pending — produced by ``control_device_smart``.
481
498
  """
482
499
  operation = get_operation_from_memory(operation_id)
483
500
 
@@ -607,31 +624,58 @@ class DeviceControlTools:
607
624
  @staticmethod
608
625
  def _normalize_bulk_parameters(
609
626
  value: Any,
610
- ) -> tuple[dict[str, Any] | None, ErrorCode | None]:
611
- """Normalize optional bulk parameters and identify invalid values."""
627
+ ) -> tuple[dict[str, Any] | None, ErrorCode | None, str]:
628
+ """Normalize a present ``parameters`` value and identify invalid ones.
629
+
630
+ Returns ``(parameters, error_code, detail)``. ``detail`` carries the
631
+ decoder's own reason and offset for a JSON failure — without it the
632
+ caller can only say "invalid JSON", which does not tell the sender
633
+ where its string went wrong. It is empty for every other outcome.
634
+ """
612
635
  if isinstance(value, str):
613
636
  try:
614
637
  value = json.loads(value)
615
- except json.JSONDecodeError:
616
- return None, ErrorCode.VALIDATION_INVALID_JSON
617
- if value is not None and not isinstance(value, dict):
618
- return None, ErrorCode.VALIDATION_INVALID_PARAMETER
619
- return value, None
638
+ except json.JSONDecodeError as exc:
639
+ return (
640
+ None,
641
+ ErrorCode.VALIDATION_INVALID_JSON,
642
+ f"{exc.msg} at position {exc.pos}",
643
+ )
644
+ except RecursionError:
645
+ # json.loads recurses per nesting level; a pathologically
646
+ # nested string must skip the row, not abort the batch.
647
+ return (
648
+ None,
649
+ ErrorCode.VALIDATION_INVALID_JSON,
650
+ "JSON nested too deeply",
651
+ )
652
+ if not isinstance(value, dict):
653
+ # A present-but-null value is malformed, same as timeout_seconds
654
+ # and validate_first; an absent key never reaches this helper.
655
+ return None, ErrorCode.VALIDATION_INVALID_PARAMETER, ""
656
+ return value, None, ""
620
657
 
621
658
  @staticmethod
622
- def _normalize_bulk_timeout(value: Any) -> tuple[float | None, bool]:
623
- """Normalize an optional non-negative finite timeout."""
624
- if value is None:
625
- return None, True
659
+ def _normalize_bulk_timeout(value: Any) -> float | None:
660
+ """Normalize a non-negative finite timeout, or ``None`` when invalid.
661
+
662
+ Bools are rejected before ``float()`` sees them: ``float(True)`` is 1.0,
663
+ which would silently accept ``timeout_seconds: true`` as a one-second
664
+ wait. Strings are rejected for the same reason — the advertised schema
665
+ is ``strict``, so coercing ``"10"`` here would silently accept what the
666
+ schema tells the model is invalid. ``None`` is invalid here too — the
667
+ caller only calls this when the key is present, and a present-but-null
668
+ timeout is a malformed row.
669
+ """
670
+ if isinstance(value, (bool, str)) or value is None:
671
+ return None
626
672
  try:
627
673
  timeout = float(value)
628
- except (TypeError, ValueError):
629
- return None, False
630
- return (
631
- (timeout, True)
632
- if math.isfinite(timeout) and timeout >= 0
633
- else (None, False)
634
- )
674
+ except (TypeError, ValueError, OverflowError):
675
+ # OverflowError: an int too large for float must skip the row,
676
+ # not abort the batch through the generic exception handler.
677
+ return None
678
+ return timeout if math.isfinite(timeout) and timeout >= 0 else None
635
679
 
636
680
  @staticmethod
637
681
  def _normalized_bulk_operation(
@@ -647,6 +691,28 @@ class DeviceControlTools:
647
691
  normalized["timeout_seconds"] = timeout
648
692
  return normalized
649
693
 
694
+ @staticmethod
695
+ def _skip_bulk_operation(
696
+ skipped_operations: list[dict[str, Any]],
697
+ op: Any,
698
+ code: ErrorCode,
699
+ error: str,
700
+ context: dict[str, Any],
701
+ suggestions: list[str] | None = None,
702
+ ) -> None:
703
+ """Log one rejected bulk row and record its structured skip entry.
704
+
705
+ ``create_error_response`` lifts every ``context`` key to the top level,
706
+ so ``index`` needs no separate write; ``operation`` is not a context
707
+ key and is attached here so callers can see the row they sent.
708
+ """
709
+ logger.warning(f"Bulk control: {error}")
710
+ err_response = create_error_response(
711
+ code, error, suggestions=suggestions, context=context
712
+ )
713
+ err_response["operation"] = op
714
+ skipped_operations.append(err_response)
715
+
650
716
  @classmethod
651
717
  def _validate_bulk_operations(
652
718
  cls,
@@ -656,41 +722,28 @@ class DeviceControlTools:
656
722
  valid: list[tuple[int, dict[str, Any], str, str]] = []
657
723
  for i, op in enumerate(operations):
658
724
  if not isinstance(op, dict):
659
- error = f"Operation at index {i} is not a dict: {type(op).__name__}"
660
- logger.warning(f"Bulk control: {error}")
661
- err_response = create_error_response(
662
- ErrorCode.VALIDATION_MISSING_PARAMETER, error, context={"index": i}
725
+ cls._skip_bulk_operation(
726
+ skipped_operations,
727
+ op,
728
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
729
+ f"Operation at index {i} is not a dict: {type(op).__name__}",
730
+ {"index": i},
663
731
  )
664
- err_response["index"] = i
665
- err_response["operation"] = op
666
- skipped_operations.append(err_response)
667
732
  continue
668
733
 
669
- allowed_keys = {
670
- "entity_id",
671
- "action",
672
- "parameters",
673
- "timeout_seconds",
674
- "validate_first",
675
- }
676
- obsolete_keys = sorted(set(op) - allowed_keys)
734
+ # str() before sorting: a direct Python caller can pass non-string
735
+ # keys, and mixed key types make sorted()/join() raise mid-batch.
736
+ obsolete_keys = sorted(str(key) for key in set(op) - _BULK_OPERATION_KEYS)
677
737
  if obsolete_keys:
678
738
  key_list = ", ".join(obsolete_keys)
679
- error = (
680
- f"Operation at index {i} contains unsupported key(s): {key_list}"
681
- )
682
- logger.warning(f"Bulk control: {error}")
683
- err_response = create_error_response(
739
+ cls._skip_bulk_operation(
740
+ skipped_operations,
741
+ op,
684
742
  ErrorCode.VALIDATION_INVALID_PARAMETER,
685
- error,
686
- suggestions=[
687
- "Use entity_id and action; put action data inside parameters"
688
- ],
689
- context={"index": i, "unsupported_keys": obsolete_keys},
743
+ f"Operation at index {i} contains unsupported key(s): {key_list}",
744
+ {"index": i, "unsupported_keys": obsolete_keys},
745
+ suggestions=[_BULK_ROW_SHAPE_SUGGESTION],
690
746
  )
691
- err_response["index"] = i
692
- err_response["operation"] = op
693
- skipped_operations.append(err_response)
694
747
  continue
695
748
 
696
749
  entity_id = op.get("entity_id")
@@ -698,83 +751,70 @@ class DeviceControlTools:
698
751
  missing = [f for f in ("entity_id", "action") if not op.get(f)]
699
752
 
700
753
  if missing:
701
- error = f"Operation at index {i} missing required fields: {', '.join(missing)}"
702
- logger.warning(f"Bulk control: {error}")
703
- err_response = create_error_response(
704
- ErrorCode.VALIDATION_MISSING_PARAMETER, error, context={"index": i}
754
+ cls._skip_bulk_operation(
755
+ skipped_operations,
756
+ op,
757
+ ErrorCode.VALIDATION_MISSING_PARAMETER,
758
+ f"Operation at index {i} missing required fields: "
759
+ f"{', '.join(missing)}",
760
+ {"index": i},
705
761
  )
706
- err_response["index"] = i
707
- err_response["operation"] = op
708
- skipped_operations.append(err_response)
709
762
  continue
710
763
 
711
764
  if not isinstance(entity_id, str) or not isinstance(action, str):
712
- error = f"Operation at index {i} requires string entity_id and action fields"
713
- logger.warning(f"Bulk control: {error}")
714
- err_response = create_error_response(
765
+ cls._skip_bulk_operation(
766
+ skipped_operations,
767
+ op,
715
768
  ErrorCode.VALIDATION_INVALID_PARAMETER,
716
- error,
717
- context={"index": i},
769
+ f"Operation at index {i} requires string entity_id and "
770
+ "action fields",
771
+ {"index": i},
718
772
  )
719
- err_response["index"] = i
720
- err_response["operation"] = op
721
- skipped_operations.append(err_response)
722
773
  continue
723
774
 
724
- parameters, parameter_error = cls._normalize_bulk_parameters(
725
- op.get("parameters")
775
+ parameters, parameter_error, parameter_detail = (
776
+ cls._normalize_bulk_parameters(op["parameters"])
777
+ if "parameters" in op
778
+ else (None, None, "")
726
779
  )
727
780
  if parameter_error is not None:
728
- error = (
729
- f"Operation at index {i} has invalid JSON parameters"
781
+ parameter_message = (
782
+ f"Operation at index {i} has invalid JSON parameters: "
783
+ f"{parameter_detail}"
730
784
  if parameter_error == ErrorCode.VALIDATION_INVALID_JSON
731
785
  else f"Operation at index {i} parameters must be a JSON object"
732
786
  )
733
- logger.warning(f"Bulk control: {error}")
734
- err_response = create_error_response(
787
+ cls._skip_bulk_operation(
788
+ skipped_operations,
789
+ op,
735
790
  parameter_error,
736
- error,
737
- context={"index": i},
791
+ parameter_message,
792
+ {"index": i},
738
793
  )
739
- err_response["index"] = i
740
- err_response["operation"] = op
741
- skipped_operations.append(err_response)
742
794
  continue
743
795
 
744
- timeout = None
745
- timeout_valid = "timeout_seconds" not in op
746
- if not timeout_valid:
747
- timeout, timeout_valid = cls._normalize_bulk_timeout(
748
- op["timeout_seconds"]
749
- )
750
- timeout_valid = timeout_valid and timeout is not None
751
- if not timeout_valid:
752
- error = (
753
- f"Operation at index {i} timeout_seconds must be a "
754
- "non-negative number"
755
- )
756
- logger.warning(f"Bulk control: {error}")
757
- err_response = create_error_response(
758
- ErrorCode.VALIDATION_INVALID_PARAMETER,
759
- error,
760
- context={"index": i},
761
- )
762
- err_response["index"] = i
763
- err_response["operation"] = op
764
- skipped_operations.append(err_response)
765
- continue
796
+ timeout: float | None = None
797
+ if "timeout_seconds" in op:
798
+ timeout = cls._normalize_bulk_timeout(op["timeout_seconds"])
799
+ if timeout is None:
800
+ cls._skip_bulk_operation(
801
+ skipped_operations,
802
+ op,
803
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
804
+ f"Operation at index {i} timeout_seconds must be a "
805
+ "non-negative number",
806
+ {"index": i},
807
+ )
808
+ continue
766
809
 
767
- if "validate_first" in op and type(op["validate_first"]) is not bool:
768
- error = f"Operation at index {i} validate_first must be a boolean"
769
- logger.warning(f"Bulk control: {error}")
770
- err_response = create_error_response(
810
+ if "validate_first" in op and not isinstance(op["validate_first"], bool):
811
+ cls._skip_bulk_operation(
812
+ skipped_operations,
813
+ op,
771
814
  ErrorCode.VALIDATION_INVALID_PARAMETER,
772
- error,
773
- context={"index": i},
815
+ f"Operation at index {i} validate_first must be a boolean",
816
+ {"index": i},
774
817
  )
775
- err_response["index"] = i
776
- err_response["operation"] = op
777
- skipped_operations.append(err_response)
778
818
  continue
779
819
 
780
820
  normalized_op = cls._normalized_bulk_operation(op, parameters, timeout)
@@ -824,8 +864,24 @@ class DeviceControlTools:
824
864
  f"mode={'parallel' if parallel else 'sequential'}",
825
865
  )
826
866
  if not valid_operations:
827
- return self._build_bulk_response(
828
- operations, results, operation_ids, skipped_operations, parallel
867
+ # Nothing dispatched, so nothing to fail soft for: the batch
868
+ # carve-out from "all tool-level failures raise" exists to
869
+ # protect successful siblings, and there are none. Raising
870
+ # before the component probe keeps the no-dispatch guarantee.
871
+ raise_tool_error(
872
+ create_error_response(
873
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
874
+ f"All {len(operations)} operation(s) failed validation; "
875
+ "nothing was dispatched",
876
+ suggestions=[
877
+ _BULK_ROW_SHAPE_SUGGESTION,
878
+ "Each operation requires 'entity_id' and 'action'",
879
+ "Check skipped_details for the per-operation reason",
880
+ "Example: {'entity_id': 'light.living_room', "
881
+ + "'action': 'on'}",
882
+ ],
883
+ context={"skipped_details": skipped_operations},
884
+ )
829
885
  )
830
886
 
831
887
  # Route through the component's bulk_call_service capability when
@@ -1439,6 +1495,7 @@ class DeviceControlTools:
1439
1495
  if skipped_operations:
1440
1496
  response["skipped_details"] = skipped_operations
1441
1497
  response["suggestions"] = [
1498
+ _BULK_ROW_SHAPE_SUGGESTION,
1442
1499
  "Some operations were skipped due to validation errors",
1443
1500
  "Each operation requires 'entity_id' and 'action' fields",
1444
1501
  "Check skipped_details for specific errors",
@@ -40,9 +40,14 @@ class _SearchBase:
40
40
  logger.debug(f"Could not fetch {label}: {result}")
41
41
  cause = str(result) or type(result).__name__
42
42
  elif isinstance(result, dict) and result.get("success"):
43
- payload = result.get("result", [])
43
+ # No default for a missing key: a legitimate empty registry is
44
+ # "result": [], so an absent result is a malformed envelope and
45
+ # must degrade loudly, not read as available-and-empty.
46
+ payload = result.get("result")
44
47
  if isinstance(payload, list):
45
48
  registry = payload
49
+ elif payload is None and "result" not in result:
50
+ cause = "malformed result payload: result key missing"
46
51
  else:
47
52
  cause = (
48
53
  "malformed result payload: expected list, "
@@ -473,13 +473,15 @@ class EntitySearchMixin(_SearchBase):
473
473
  area_registry = self._parse_area_registry(results[1], registry_warnings)
474
474
  entity_reg_map = self._parse_entity_reg_map(results[2], registry_warnings)
475
475
  device_area_map = self._parse_device_area_map(results[3], registry_warnings)
476
- floor_result = results[4]
477
- floor_registry_available = (
478
- isinstance(floor_result, dict)
479
- and floor_result.get("success") is True
480
- and isinstance(floor_result.get("result"), list)
481
- )
482
- floor_registry = self._parse_floor_registry(floor_result, registry_warnings)
476
+ # Availability is read back OUT of the parse rather than re-derived
477
+ # from the raw payload: the parser already decides what counts as
478
+ # usable floor data, and a second predicate here drifted from it
479
+ # once. An empty warnings list means the parser accepted the
480
+ # payload; anything else disables the fuzzy floor/area fallback.
481
+ floor_warnings: list[str] = []
482
+ floor_registry = self._parse_floor_registry(results[4], floor_warnings)
483
+ floor_registry_available = not floor_warnings
484
+ registry_warnings.extend(floor_warnings)
483
485
  degraded_warnings = registry_warnings + visibility_warnings
484
486
 
485
487
  matched_area_ids, resolution_warnings = self._resolve_area_query(
@@ -657,8 +659,15 @@ class EntitySearchMixin(_SearchBase):
657
659
 
658
660
  if not floor_registry_available:
659
661
  # Without the floor registry, fuzzy area matching could silently
660
- # recreate the original floor->partial-area misresolution.
661
- return set(), []
662
+ # recreate the original floor->partial-area misresolution. The
663
+ # fetch warning names the outage; this one names what the outage
664
+ # cost the caller, so "no match" is not read as "no such area".
665
+ return set(), [
666
+ f"Floor data is unavailable, so '{area_query}' was matched only "
667
+ "against exact area IDs, names, and aliases; close-spelling "
668
+ "matching was skipped. Retry, or pass an exact area_id from "
669
+ "ha_list_floors_areas."
670
+ ]
662
671
 
663
672
  fuzzy_floor_ids, floor_score = cls._match_fuzzy_registry_ids(
664
673
  floor_registry, "floor_id", query_lower
@@ -768,7 +777,10 @@ class EntitySearchMixin(_SearchBase):
768
777
  """Resolve the query to area IDs: exact id/name/alias, then fuzzy.
769
778
 
770
779
  Exact matches delegate to the shared registry matcher so area and floor
771
- precedence use identical case-insensitive semantics.
780
+ precedence use identical case-insensitive semantics. Exact must win
781
+ outright: a query like "bedroom_kids" partial-matches its parent
782
+ "bedroom" at ratio 100, so falling through to the fuzzy pass would
783
+ aggregate every sibling area's entities under an exact hit.
772
784
  """
773
785
  exact_area_ids = cls._match_exact_registry_ids(
774
786
  area_registry, "area_id", area_query_lower
@@ -321,12 +321,11 @@ def _finalize_partial_state(
321
321
  if partial_local:
322
322
  response["partial"] = True
323
323
  response["errors"].extend(errors_local)
324
- local_reason = "; ".join(
325
- f"{error['surface']}: {error['error']}" for error in errors_local
326
- )
327
- existing_reason = response.get("partial_reason")
328
- response["partial_reason"] = (
329
- f"{existing_reason}; {local_reason}" if existing_reason else local_reason
324
+ _merge_partial_reason(
325
+ response,
326
+ "; ".join(
327
+ f"{error['surface']}: {error['error']}" for error in errors_local
328
+ ),
330
329
  )
331
330
 
332
331
 
@@ -2217,7 +2216,11 @@ class SearchTools:
2217
2216
  raise outcome
2218
2217
  if isinstance(outcome, Exception):
2219
2218
  partial = True
2220
- errors.append({"surface": label, "error": str(outcome)})
2219
+ # ``str(asyncio.TimeoutError())`` is "" — fall back to the type
2220
+ # name so partial_reason never reads "entities: ".
2221
+ errors.append(
2222
+ {"surface": label, "error": str(outcome) or type(outcome).__name__}
2223
+ )
2221
2224
  logger.warning("ha_search %s branch failed: %r", label, outcome)
2222
2225
  continue
2223
2226
  _apply_search_outcome(response, label, outcome)