ha-mcp-dev 8.3.0.dev2444__tar.gz → 8.3.0.dev2447__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 (291) hide show
  1. {ha_mcp_dev-8.3.0.dev2444/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2447}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/rest_client.py +262 -50
  4. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +1 -1
  5. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +1 -1
  6. ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/resources/skills-vendor/AGENTS.md +40 -0
  7. ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/resources/skills-vendor/CLAUDE.md +40 -0
  8. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/README.md +1 -0
  9. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +14 -8
  10. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +30 -0
  11. ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/backups.md +128 -0
  12. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +4 -1
  13. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +1 -1
  14. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/server.py +192 -2
  15. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_server.py +30 -0
  16. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/en.json +4 -1
  17. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/settings.js +158 -44
  18. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/backup.py +18 -3
  19. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/error_log_parsing.py +208 -4
  20. ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/log_common.py +199 -0
  21. ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/log_sources.py +764 -0
  22. ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/log_sources_supervisor.py +326 -0
  23. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/registry.py +12 -0
  24. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_bug_report.py +15 -6
  25. ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/tools_logs.py +296 -0
  26. ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/tools_utility.py +277 -0
  27. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/skill_loader.py +22 -4
  28. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  29. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/SOURCES.txt +5 -0
  30. ha_mcp_dev-8.3.0.dev2444/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -57
  31. ha_mcp_dev-8.3.0.dev2444/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -57
  32. ha_mcp_dev-8.3.0.dev2444/src/ha_mcp/tools/tools_utility.py +0 -1600
  33. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/LICENSE +0 -0
  34. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/MANIFEST.in +0 -0
  35. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/README.md +0 -0
  36. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/setup.cfg +0 -0
  37. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/__init__.py +0 -0
  38. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/__main__.py +0 -0
  39. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_pypi_marker +0 -0
  40. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_rich_compat.py +0 -0
  41. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/__init__.py +0 -0
  42. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/requirements.txt +0 -0
  43. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  44. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  45. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  46. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  47. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  48. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  49. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  50. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  51. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  52. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  53. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  54. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  55. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  56. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  57. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  58. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  59. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  60. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  61. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  62. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  63. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  64. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  65. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  66. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  67. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  68. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  69. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  70. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  71. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  72. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  73. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  74. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  75. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  76. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  77. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  78. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  79. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  80. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  81. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  82. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  83. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  84. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  85. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  86. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  87. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  88. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  89. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  90. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  91. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  92. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  93. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  94. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  95. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  96. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  97. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  98. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  99. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_version.py +0 -0
  100. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/auth/__init__.py +0 -0
  101. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/auth/consent_form.py +0 -0
  102. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/auth/oidc_compat.py +0 -0
  103. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/auth/provider.py +0 -0
  104. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/backup_manager.py +0 -0
  105. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/browser_landing.py +0 -0
  106. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/__init__.py +0 -0
  107. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/supervisor_client.py +0 -0
  108. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/websocket_client.py +0 -0
  109. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/websocket_listener.py +0 -0
  110. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/config.py +0 -0
  111. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  112. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  113. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  114. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  115. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  116. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  117. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/errors.py +0 -0
  118. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  119. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/llm_exposure.py +0 -0
  120. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/log_filters.py +0 -0
  121. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/__init__.py +0 -0
  122. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/approval_queue.py +0 -0
  123. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/editing.py +0 -0
  124. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/evaluator.py +0 -0
  125. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/handlers.py +0 -0
  126. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/middleware.py +0 -0
  127. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/model.py +0 -0
  128. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/persistence.py +0 -0
  129. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/value_sources.py +0 -0
  130. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/py.typed +0 -0
  131. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/read_only.py +0 -0
  132. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/redaction.py +0 -0
  133. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/renamed_tools.py +0 -0
  134. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  135. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  136. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  137. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  138. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  139. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  140. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  141. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  142. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  143. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  144. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  145. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  146. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  147. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  148. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  149. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  150. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/__init__.py +0 -0
  151. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  152. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  153. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  154. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  155. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  156. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  157. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
  158. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  159. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  160. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_theme.py +0 -0
  161. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  162. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  163. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  164. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  165. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  166. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  167. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  168. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  169. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
  170. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  171. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  172. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  173. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  174. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
  175. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  176. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/settings.css +0 -0
  177. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/settings.html +0 -0
  178. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/smoke_test.py +0 -0
  179. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  180. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/strict_bps.py +0 -0
  181. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/__init__.py +0 -0
  182. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/auto_backup.py +0 -0
  183. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  184. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/best_practice_result.py +0 -0
  185. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  186. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/bulk_selector.py +0 -0
  187. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_api.py +0 -0
  188. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_config_entries.py +0 -0
  189. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_config_reads.py +0 -0
  190. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_devices.py +0 -0
  191. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_registries.py +0 -0
  192. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  193. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  194. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  195. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  196. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  197. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  198. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  199. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  200. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/device_control.py +0 -0
  201. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/hacs_registration.py +0 -0
  202. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/helpers.py +0 -0
  203. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  204. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/__init__.py +0 -0
  205. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/base.py +0 -0
  206. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/matter.py +0 -0
  207. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/thread.py +0 -0
  208. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  209. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/zwave.py +0 -0
  210. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/reference_validator.py +0 -0
  211. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  212. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  213. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  214. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  215. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  216. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  217. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  218. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
  219. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  220. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  221. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  222. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  223. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_addons.py +0 -0
  224. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_areas.py +0 -0
  225. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  226. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  227. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_calendar.py +0 -0
  228. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_camera.py +0 -0
  229. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_categories.py +0 -0
  230. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_code.py +0 -0
  231. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  232. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  233. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  234. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  235. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  236. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  237. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_dev.py +0 -0
  238. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_energy.py +0 -0
  239. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_entities.py +0 -0
  240. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  241. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_groups.py +0 -0
  242. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_hacs.py +0 -0
  243. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_history.py +0 -0
  244. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_integrations.py +0 -0
  245. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_labels.py +0 -0
  246. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_radio.py +0 -0
  247. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_registry.py +0 -0
  248. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_resources.py +0 -0
  249. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_search.py +0 -0
  250. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  251. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_service.py +0 -0
  252. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_services.py +0 -0
  253. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_system.py +0 -0
  254. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_themes.py +0 -0
  255. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_todo.py +0 -0
  256. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_traces.py +0 -0
  257. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_updates.py +0 -0
  258. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  259. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  260. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  261. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_zones.py +0 -0
  262. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/util_helpers.py +0 -0
  263. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/validation_middleware.py +0 -0
  264. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/transforms/__init__.py +0 -0
  265. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/transforms/categorized_search.py +0 -0
  266. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  267. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/transport_security.py +0 -0
  268. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/update_check.py +0 -0
  269. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/__init__.py +0 -0
  270. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/config_hash.py +0 -0
  271. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/config_write_lock.py +0 -0
  272. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/data_paths.py +0 -0
  273. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/domain_handlers.py +0 -0
  274. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/entity_membership.py +0 -0
  275. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  276. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  277. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/operation_manager.py +0 -0
  278. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/python_sandbox.py +0 -0
  279. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/usage_logger.py +0 -0
  280. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/__init__.py +0 -0
  281. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/enforcement.py +0 -0
  282. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/model.py +0 -0
  283. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/persistence.py +0 -0
  284. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/resolver.py +0 -0
  285. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  286. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  287. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  288. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  289. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/tests/__init__.py +0 -0
  290. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/tests/test_constants.py +0 -0
  291. {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/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.dev2444
3
+ Version: 8.3.0.dev2447
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.dev2444"
7
+ version = "8.3.0.dev2447"
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
@@ -8,6 +8,7 @@ import logging
8
8
  import os
9
9
  import ssl
10
10
  import time
11
+ from collections.abc import Awaitable, Callable
11
12
  from dataclasses import dataclass
12
13
  from typing import Any, NoReturn
13
14
 
@@ -52,10 +53,23 @@ logger = logging.getLogger(__name__)
52
53
  _RETRYABLE_GATEWAY_STATUS = frozenset({502, 503, 504})
53
54
  _SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
54
55
  _MAX_REQUEST_ATTEMPTS = 3
55
- # Journald window requested for the Core error log on Supervisor-backed
56
- # installs. Both such branches of get_error_log() build their request from this
57
- # constant, so the window they ask for cannot drift apart.
58
- _ERROR_LOG_LINES = 20000
56
+ # Smallest log window this client requests. Supervisor's Range syntax addresses
57
+ # a newest-anchored window by its first entry, so a one-entry one is
58
+ # inexpressible (``entries=:0:1`` reads from the OLDEST entry instead);
59
+ # Supervisor coerces its own ``?lines=1`` to 2 (supervisor/api/host.py) and this
60
+ # client mirrors that on every route, so all install types serve the same slice.
61
+ # Shared with the tool layer, which sizes its fetch window against it.
62
+ MIN_LOG_WINDOW_LINES = 2
63
+ # Entries the has_more probe reads from just behind a full window. A one-entry
64
+ # probe compared one rendered line, which a boundary duplicate (the same
65
+ # timestamp-less traceback line logged twice) could false-match into "end of
66
+ # history". Comparing a block this deep requires that many CONSECUTIVE
67
+ # identical lines to straddle the boundary — and then the history a false stop
68
+ # skips is more of the same duplicates. Timestamps cannot close the gap
69
+ # instead: the colliding lines are exactly the ones without them, and
70
+ # journald's cursor/timestamp metadata exists only in the journal export
71
+ # format, which the hassio-proxy route cannot request.
72
+ _PROBE_ENTRIES = 8
59
73
 
60
74
 
61
75
  class HomeAssistantError(Exception):
@@ -96,7 +110,7 @@ class HomeAssistantAuthError(HomeAssistantError):
96
110
  auth errors as part of the local "this entity is not registered yet"
97
111
  polling logic. Sites that specifically need to catch both must list
98
112
  them explicitly (see ``_get_supervisor_log`` and
99
- ``_get_system_service_log`` in ``tools_utility.py``).
113
+ ``_get_system_service_log`` in ``log_sources_supervisor.py``).
100
114
  """
101
115
 
102
116
 
@@ -114,6 +128,19 @@ class HomeAssistantAPIError(HomeAssistantError):
114
128
  self.response_data = response_data
115
129
 
116
130
 
131
+ @dataclass(frozen=True)
132
+ class ErrorLogPage:
133
+ """One bounded window of the error log, plus whether older history exists.
134
+
135
+ ``has_more`` cannot be derived by the caller from ``text``: a journald
136
+ window that comes back full may or may not have anything behind it (see
137
+ ``_journald_error_log_page``), and only the fetching route knows which.
138
+ """
139
+
140
+ text: str
141
+ has_more: bool
142
+
143
+
117
144
  @dataclass(frozen=True)
118
145
  class SceneResolution:
119
146
  """Outcome of resolving a scene identifier via the entity registry.
@@ -579,8 +606,140 @@ class HomeAssistantClient:
579
606
  logger.debug("Checking configuration")
580
607
  return await self._request("POST", "/config/core/check_config")
581
608
 
582
- async def get_error_log(self) -> str:
583
- """Get Home Assistant error log.
609
+ @staticmethod
610
+ def _journald_range_header(lines: int, offset: int = 0) -> str:
611
+ """Build Supervisor's journald ``Range`` header for a bounded window.
612
+
613
+ Supervisor's log endpoints accept
614
+ ``Range: entries=<cursor>:<num_skip>:<num_entries>`` and build
615
+ ``entries=:-{N-1}:{N}`` themselves for a ``?lines=N`` request
616
+ (supervisor/api/host.py). ``offset`` moves the window that many entries
617
+ further back, so the returned slice ends ``offset`` entries before the
618
+ newest one.
619
+
620
+ A ``lines=1`` window is only expressible with a non-zero ``offset``: at
621
+ ``offset=0`` the header degenerates to ``entries=:0:1``, whose
622
+ non-negative skip reads from the OLDEST entry instead of the newest.
623
+ Callers anchoring at the newest entry pass ``lines`` already floored at
624
+ ``MIN_LOG_WINDOW_LINES``; the one-entry probe always carries an offset.
625
+ """
626
+ # Negate the computed skip rather than prefixing "-" so the degenerate
627
+ # skip of 0 renders as ":0:" (non-negative, reads from the oldest
628
+ # entry) instead of the ill-formed ":-0:".
629
+ return f"entries=:{-(offset + lines - 1)}:{lines}"
630
+
631
+ @staticmethod
632
+ def _container_error_log_page(text: str, lines: int, offset: int) -> ErrorLogPage:
633
+ """Window the whole-file ``/api/error_log`` body client-side.
634
+
635
+ Same tail semantics as the journald Range the Supervisor-backed
636
+ branches request, so ``get_error_log`` answers with the same slice of
637
+ history on every install type. Paging is exact here — the whole file is
638
+ in hand, so ``has_more`` is simply whether anything precedes the window.
639
+ An ``offset`` past the start of the file yields an empty final page.
640
+ """
641
+ all_lines = text.splitlines()
642
+ end = len(all_lines) - offset
643
+ if end <= 0:
644
+ return ErrorLogPage(text="", has_more=False)
645
+ start = max(end - lines, 0)
646
+ return ErrorLogPage(text="\n".join(all_lines[start:end]), has_more=start > 0)
647
+
648
+ async def _supervisor_core_logs(self, lines: int, offset: int) -> str:
649
+ """Fetch a journald window of HA Core's log straight from Supervisor."""
650
+ return await self._supervisor_logs_get("core", lines=lines, offset=offset)
651
+
652
+ async def _proxied_core_logs(self, lines: int, offset: int) -> str:
653
+ """Fetch the same window through HA Core's hassio proxy with the LLA."""
654
+ response = await self._raw_request(
655
+ "GET",
656
+ "/hassio/core/logs",
657
+ headers={
658
+ "Accept": "text/plain",
659
+ "Range": self._journald_range_header(lines, offset),
660
+ },
661
+ )
662
+ return response.text
663
+
664
+ async def _journald_error_log_page(
665
+ self,
666
+ lines: int,
667
+ offset: int,
668
+ fetch: Callable[[int, int], Awaitable[str]],
669
+ ) -> ErrorLogPage:
670
+ """Read one journald window and settle ``has_more`` with a 1-entry probe.
671
+
672
+ systemd's journal-gatewayd does not guard the negative-skip branch the
673
+ way it guards the positive one (``sd_journal_previous_skip(-n_skip + 1)``
674
+ returns no END_OF_STREAM): an offset past the start of the journal
675
+ CLAMPS to the oldest entry and still answers with a full window. So a
676
+ saturated window proves nothing about what lies behind it, and a
677
+ count-based ``has_more`` would stay true forever — an agent following
678
+ the pagination hint would loop on identical pages.
679
+
680
+ Hence: a short or empty window means the journal really is exhausted
681
+ (nothing clamps a window smaller), and a saturated one is settled by
682
+ asking for the ``_PROBE_ENTRIES`` entries that should sit just behind
683
+ it. If the journal has nothing there, the clamp starts the probe at
684
+ the same oldest entry the window starts with, so the probe block
685
+ equals the window's opening lines — identity means end of history. A
686
+ differing block proves deeper history (an under-full span behind the
687
+ window also clamps and misaligns, which correctly reads as "more").
688
+ The identity check also terminates the degraded case where an
689
+ intermediary strips the ``Range`` header (RFC 7233 permits it): both
690
+ requests then return Supervisor's default window and compare equal.
691
+
692
+ The saturation test counts lines only to decide whether to probe, never
693
+ to answer: one journald entry can be a multi-line traceback, so the line
694
+ count over-estimates entries and can only over-trigger the probe, which
695
+ then gives the real answer. In practice the two rarely diverge here:
696
+ docker's journald log driver — how HAOS captures Core's output —
697
+ ingests stdout line by line, one journal entry per rendered line, so
698
+ multi-line entries reach this endpoint only from native journal-API
699
+ writers.
700
+
701
+ Known imprecision, accepted: the identity check can still stop one
702
+ page early when the compared block — ``_PROBE_ENTRIES`` lines, or the
703
+ window's own length when that is smaller — is byte-identical across
704
+ the boundary (see the constant's comment for why timestamps cannot
705
+ disambiguate instead). The failure
706
+ direction is a missed page of exactly those duplicates — bounded,
707
+ unlike the infinite loop a count-based ``has_more`` produces on the
708
+ clamp.
709
+ """
710
+ text = await fetch(lines, offset)
711
+ if not text or len(text.splitlines()) < lines:
712
+ return ErrorLogPage(text=text, has_more=False)
713
+ probe = await fetch(_PROBE_ENTRIES, offset + lines)
714
+ probe_lines = probe.splitlines()
715
+ window_lines = text.splitlines()
716
+ # Compare the shared-length prefix: with a window smaller than the
717
+ # probe (lines < _PROBE_ENTRIES), an end-of-history clamp returns a
718
+ # probe LONGER than the window, and a full-length compare would read
719
+ # that as "more history" forever — the small-limit loop this method
720
+ # exists to prevent.
721
+ shared = min(len(probe_lines), len(window_lines))
722
+ return ErrorLogPage(
723
+ text=text,
724
+ has_more=shared > 0 and probe_lines[:shared] != window_lines[:shared],
725
+ )
726
+
727
+ async def get_error_log(self, lines: int, offset: int = 0) -> ErrorLogPage:
728
+ """Get a bounded window of the Home Assistant error log.
729
+
730
+ ``lines`` is the number of log lines to fetch and ``offset`` how many
731
+ newer lines to skip, counted back from the most recent one; both are
732
+ floored/served at ``MIN_LOG_WINDOW_LINES``. Returns the window text and
733
+ whether older history remains behind it. The window is mandatory: the
734
+ unconditional 20,000-line fetch this replaces made Supervisor assemble
735
+ a journald slice for 15+ minutes on real hardware (#2279).
736
+
737
+ The whole body runs under one ``asyncio.timeout(self.timeout)``. That
738
+ is the only bound that holds here: ``httpx.Timeout`` applies per I/O
739
+ operation, so a server trickling bytes while it assembles the body
740
+ resets the read timeout indefinitely and no per-attempt timeout ever
741
+ fires — the mechanism behind #2279's hang. The deadline covers the
742
+ install-class probe, the log fetch and the ``has_more`` probe together.
584
743
 
585
744
  Three-way branch depending on how this client reaches HA:
586
745
 
@@ -601,6 +760,17 @@ class HomeAssistantClient:
601
760
  - **External client → Container/pip HA** (neither of the above):
602
761
  keep the historical ``/api/error_log`` proxy path.
603
762
 
763
+ The two Supervisor-backed branches express the window as a journald
764
+ ``Range`` header (see ``_journald_range_header``) and settle
765
+ ``has_more`` with a one-entry probe (see ``_journald_error_log_page``).
766
+ HA Core's hassio proxy forwards that header for its log paths
767
+ (``PATHS_LOGS`` in ``homeassistant/components/hassio/http.py``); an
768
+ intermediary that stripped it would degrade to Supervisor's 100-line
769
+ default, never to an unbounded fetch, and the probe still terminates
770
+ paging. ``/api/error_log`` is a whole-file plain-text endpoint with no
771
+ window support, so the container branch reads the file and applies the
772
+ same window client-side, where paging is exact.
773
+
604
774
  The middle branch was discovered by the HAOS E2E tier (#1326): the
605
775
  test harness runs ha-mcp externally against a booted HAOS, hits
606
776
  the unregistered endpoint, and the old binary branch surfaced as
@@ -615,30 +785,36 @@ class HomeAssistantClient:
615
785
  HomeAssistantConnectionError: Network, timeout, or transport
616
786
  error.
617
787
  """
618
- if is_running_in_addon():
619
- logger.debug("Fetching error log via Supervisor direct (core service)")
620
- # An explicit `lines` is required: without it Supervisor applies its
621
- # 100-line default, which is far too short a slice to tell what keeps
622
- # repeating. `_get_supervisor_log` plumbs the same parameter for the
623
- # same reason (#1734).
624
- return await self._supervisor_logs_get("core", lines=_ERROR_LOG_LINES)
625
-
626
- if await self._is_supervised_install():
627
- logger.debug(
628
- "Fetching error log via HA Core /hassio/core/logs proxy (supervised)"
629
- )
630
- raw_response = await self._raw_request(
631
- "GET",
632
- f"/hassio/core/logs?lines={_ERROR_LOG_LINES}",
633
- headers={"Accept": "text/plain"},
634
- )
635
- return raw_response.text
788
+ window = max(lines, MIN_LOG_WINDOW_LINES)
789
+ try:
790
+ async with asyncio.timeout(self.timeout):
791
+ if is_running_in_addon():
792
+ logger.debug(
793
+ "Fetching error log via Supervisor direct (core service)"
794
+ )
795
+ return await self._journald_error_log_page(
796
+ window, offset, self._supervisor_core_logs
797
+ )
636
798
 
637
- logger.debug("Fetching error log via HA Core proxy (Container/pip)")
638
- raw_response = await self._raw_request(
639
- "GET", "/error_log", headers={"Accept": "text/plain"}
640
- )
641
- return raw_response.text
799
+ if await self._is_supervised_install():
800
+ logger.debug(
801
+ "Fetching error log via HA Core /hassio/core/logs proxy "
802
+ "(supervised)"
803
+ )
804
+ return await self._journald_error_log_page(
805
+ window, offset, self._proxied_core_logs
806
+ )
807
+
808
+ logger.debug("Fetching error log via HA Core proxy (Container/pip)")
809
+ raw_response = await self._raw_request(
810
+ "GET", "/error_log", headers={"Accept": "text/plain"}
811
+ )
812
+ return self._container_error_log_page(raw_response.text, window, offset)
813
+ except TimeoutError as e:
814
+ raise HomeAssistantConnectionError(
815
+ f"Timeout fetching the error log after {self.timeout}s: "
816
+ f"{str(e) or type(e).__name__}"
817
+ ) from e
642
818
 
643
819
  async def _is_supervised_install(self) -> bool:
644
820
  """Detect whether the target HA is a Supervised / HAOS install.
@@ -742,12 +918,30 @@ class HomeAssistantClient:
742
918
  return await self._get_addon_logs_via_supervisor(slug, lines=lines)
743
919
 
744
920
  logger.debug(f"Fetching addon logs for slug={slug} via HA Core proxy")
745
- response = await self._raw_request(
746
- "GET",
747
- f"/hassio/addons/{slug}/logs",
748
- headers={"Accept": "text/plain"},
749
- params={"lines": lines} if lines is not None else None,
750
- )
921
+ return await self._proxied_logs_get(f"addons/{slug}", lines)
922
+
923
+ async def _proxied_logs_get(self, path: str, lines: int | None) -> str:
924
+ """Fetch ``text/plain`` logs through HA Core's hassio proxy.
925
+
926
+ Wrapped in an overall wall-clock deadline for the same reason as the
927
+ error-log routes (#2279): ``httpx.Timeout`` applies per I/O operation,
928
+ so a server trickling bytes while assembling the body resets the read
929
+ timeout indefinitely, and the deadline is the only bound that holds.
930
+ It caps the whole ``_raw_request`` retry envelope.
931
+ """
932
+ try:
933
+ async with asyncio.timeout(self.timeout):
934
+ response = await self._raw_request(
935
+ "GET",
936
+ f"/hassio/{path}/logs",
937
+ headers={"Accept": "text/plain"},
938
+ params={"lines": lines} if lines is not None else None,
939
+ )
940
+ except TimeoutError as e:
941
+ raise HomeAssistantConnectionError(
942
+ f"Timeout fetching /hassio/{path}/logs from Home Assistant "
943
+ f"after {self.timeout}s: {str(e) or type(e).__name__}"
944
+ ) from e
751
945
  return response.text
752
946
 
753
947
  @staticmethod
@@ -772,7 +966,12 @@ class HomeAssistantClient:
772
966
  message = text_body.strip() or reason_phrase or "<empty body>"
773
967
  return message
774
968
 
775
- async def _supervisor_logs_get(self, path: str, lines: int | None = None) -> str:
969
+ async def _supervisor_logs_get(
970
+ self,
971
+ path: str,
972
+ lines: int | None = None,
973
+ offset: int | None = None,
974
+ ) -> str:
776
975
  """Fetch ``text/plain`` logs from a Supervisor REST endpoint.
777
976
 
778
977
  ``path`` is everything between ``http://supervisor/`` and ``/logs``:
@@ -782,11 +981,22 @@ class HomeAssistantClient:
782
981
  cli, multicast, observer}) for system-service logs
783
982
 
784
983
  ``lines`` maps to the endpoint's ``?lines=`` journald-window query
785
- param; omitted → Supervisor's 100-line default window.
984
+ param; omitted → Supervisor's 100-line default window. Adding
985
+ ``offset`` switches the same window to a ``Range: entries=`` header
986
+ (see ``_journald_range_header``), which is the only form that can
987
+ address an older window. The two forms are mutually exclusive on the
988
+ wire — Supervisor tests ``if "lines" in request.query`` first and
989
+ ignores a ``Range`` header whenever that query param is present — so
990
+ the offset form sends the window in the header and no query param,
991
+ rather than a pair that could contradict each other.
786
992
 
787
993
  Bypasses ``HomeAssistantClient.httpx_client`` because that client targets
788
994
  Home Assistant Core through ``http://supervisor/core/api``, while logs
789
- belong to Supervisor at ``http://supervisor``. Both clients use the same
995
+ belong to Supervisor at ``http://supervisor``. Plain HTTP here is the
996
+ platform contract, not a downgrade: Supervisor serves its API over
997
+ HTTP only (no TLS endpoint or IPC socket exists), on the internal
998
+ ``hassio`` docker network whose isolation is the boundary protecting
999
+ the token — see SECURITY.md § "Local network is the trusted zone". Both clients use the same
790
1000
  ``SUPERVISOR_TOKEN``; the Core proxy requires ``homeassistant_api``, while
791
1001
  system-service and arbitrary app-log paths require ``hassio_api`` and
792
1002
  ``hassio_role: manager``. The recognized-app-token exception is
@@ -815,11 +1025,19 @@ class HomeAssistantClient:
815
1025
  )
816
1026
 
817
1027
  relative_path = f"/{path}/logs"
1028
+ headers = {"Accept": "text/plain"}
1029
+ params = {"lines": lines} if lines is not None else None
1030
+ if offset is not None:
1031
+ # An offset window with no size asked for falls back to the
1032
+ # smallest one the Range syntax can express.
1033
+ size = lines if lines is not None else MIN_LOG_WINDOW_LINES
1034
+ headers["Range"] = self._journald_range_header(size, offset)
1035
+ params = None
818
1036
  logger.debug(
819
- "Fetching %s%s via Supervisor direct (lines=%s)",
1037
+ "Fetching %s%s via Supervisor direct (%s)",
820
1038
  get_supervisor_base_url(),
821
1039
  relative_path,
822
- lines,
1040
+ f"range={headers['Range']}" if offset is not None else f"lines={lines}",
823
1041
  )
824
1042
 
825
1043
  try:
@@ -830,8 +1048,8 @@ class HomeAssistantClient:
830
1048
  ) as client:
831
1049
  response = await client.get(
832
1050
  relative_path,
833
- headers={"Accept": "text/plain"},
834
- params={"lines": lines} if lines is not None else None,
1051
+ headers=headers,
1052
+ params=params,
835
1053
  )
836
1054
  except (TimeoutError, httpx.TimeoutException) as e:
837
1055
  raise HomeAssistantConnectionError(
@@ -924,13 +1142,7 @@ class HomeAssistantClient:
924
1142
  return await self._supervisor_logs_get(service, lines=lines)
925
1143
 
926
1144
  logger.debug(f"Fetching {service} logs via HA Core proxy")
927
- response = await self._raw_request(
928
- "GET",
929
- f"/hassio/{service}/logs",
930
- headers={"Accept": "text/plain"},
931
- params={"lines": lines} if lines is not None else None,
932
- )
933
- return response.text
1145
+ return await self._proxied_logs_get(service, lines)
934
1146
 
935
1147
  async def test_connection(self) -> tuple[bool, str | None]:
936
1148
  """
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Agent skills for Home Assistant: best practices for automations, helpers, templates, device control, and dashboards",
9
- "version": "0.15.0"
9
+ "version": "0.18.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "home-assistant-skills",
3
3
  "description": "Agent skills for Home Assistant: best practices for automations, helpers, templates, device control, and dashboards.",
4
- "version": "0.15.0",
4
+ "version": "0.18.0",
5
5
  "repository": "https://github.com/homeassistant-ai/skills",
6
6
  "license": "MIT"
7
7
  }
@@ -0,0 +1,40 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Skill Format
6
+
7
+ Every skill is a `SKILL.md` file with YAML frontmatter:
8
+
9
+ ```yaml
10
+ ---
11
+ name: skill-name-here
12
+ description: >
13
+ When to activate this skill and what symptoms it addresses.
14
+ ---
15
+ ```
16
+
17
+ Full authoring constraints: `CONTRIBUTING.md`. The one CI cannot catch:
18
+ - `metadata.version` must be `0` on new skills — do not edit manually; CI assigns the real version on merge
19
+
20
+ ## Skill Authoring Principles
21
+
22
+ - **Context window conservation** — only include domain-specific knowledge that agents lack; omit general programming advice
23
+ - **Conciseness** — provide patterns and quick-reference tables, not tutorials
24
+ - **Consistent terminology** — one term per concept throughout a skill
25
+ - **Symptom-based triggering** — the `description` frontmatter should describe observable agent behaviors that signal the skill is needed
26
+ - **No tool names** — reference HA REST APIs and concepts, never specific MCP tool names (e.g. `ha_rename_entity`); tool names vary by agent setup
27
+
28
+ ## Validation
29
+
30
+ To validate locally:
31
+
32
+ ```bash
33
+ uvx skills-ref validate skills/<skill-name>
34
+ ```
35
+
36
+ ## Reviewing Skill PRs
37
+
38
+ - Judge prose as agent-consumed context, not human docs — the Skill Authoring Principles above are the review bar (e.g. an operator→result lookup table beats narrative bullets, because agents land here holding one case to resolve)
39
+ - Skills make version-pinned claims about HA behavior — verify against source at the release tag: `gh api repos/home-assistant/core/contents/<path>?ref=2026.7.4 --jq .content | base64 -d`
40
+ - Community PRs come from forks: base-repo `?ref=<pr-branch>` 404s; get the fork with `gh pr view <pr> --json headRepository` and fetch files from there
@@ -0,0 +1,40 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Skill Format
6
+
7
+ Every skill is a `SKILL.md` file with YAML frontmatter:
8
+
9
+ ```yaml
10
+ ---
11
+ name: skill-name-here
12
+ description: >
13
+ When to activate this skill and what symptoms it addresses.
14
+ ---
15
+ ```
16
+
17
+ Full authoring constraints: `CONTRIBUTING.md`. The one CI cannot catch:
18
+ - `metadata.version` must be `0` on new skills — do not edit manually; CI assigns the real version on merge
19
+
20
+ ## Skill Authoring Principles
21
+
22
+ - **Context window conservation** — only include domain-specific knowledge that agents lack; omit general programming advice
23
+ - **Conciseness** — provide patterns and quick-reference tables, not tutorials
24
+ - **Consistent terminology** — one term per concept throughout a skill
25
+ - **Symptom-based triggering** — the `description` frontmatter should describe observable agent behaviors that signal the skill is needed
26
+ - **No tool names** — reference HA REST APIs and concepts, never specific MCP tool names (e.g. `ha_rename_entity`); tool names vary by agent setup
27
+
28
+ ## Validation
29
+
30
+ To validate locally:
31
+
32
+ ```bash
33
+ uvx skills-ref validate skills/<skill-name>
34
+ ```
35
+
36
+ ## Reviewing Skill PRs
37
+
38
+ - Judge prose as agent-consumed context, not human docs — the Skill Authoring Principles above are the review bar (e.g. an operator→result lookup table beats narrative bullets, because agents land here holding one case to resolve)
39
+ - Skills make version-pinned claims about HA behavior — verify against source at the release tag: `gh api repos/home-assistant/core/contents/<path>?ref=2026.7.4 --jq .content | base64 -d`
40
+ - Community PRs come from forks: base-repo `?ref=<pr-branch>` 404s; get the fork with `gh pr view <pr> --json headRepository` and fetch files from there
@@ -76,6 +76,7 @@ The `home-assistant-best-practices` skill includes:
76
76
  | `references/examples.yaml` | Compound examples combining multiple best practices |
77
77
  | `references/appdaemon.md` | AppDaemon apps: when to use vs. native HA, app structure, service calls, scheduling, error handling, safe refactoring impact |
78
78
  | `references/blueprint-guide.md` | Blueprint authoring: metadata & `source_url`, inputs & selectors, `target` vs `entity`, defaults, `!input` templating, versioning |
79
+ | `references/backups.md` | Full instance backups vs rolling one object back, when an operation needs a backup first, what an archive contains, encryption keys, restore verification, backup deletion |
79
80
 
80
81
  ## Contributing
81
82
 
@@ -5,14 +5,14 @@ description: >
5
5
 
6
6
  TRIGGER THIS SKILL WHEN:
7
7
  - Creating or editing automations, scripts, scenes, or dashboards
8
- - Choosing between template sensors and built-in helpers
9
- - Restructuring triggers, conditions, or automation modes
10
- - Setting up Zigbee button/remote automations
8
+ - Choosing template sensors vs built-in helpers
9
+ - Restructuring triggers, conditions, or modes
10
+ - Zigbee button/remote automations
11
11
  - Renaming entities or migrating device_id to entity_id
12
- - Configuring dashboard cards or selecting helpers
13
12
  - Looking up card types or domain docs
14
- - Writing or reviewing AppDaemon apps
15
- - Authoring or editing reusable Blueprints
13
+ - AppDaemon apps
14
+ - Authoring reusable Blueprints
15
+ - About to delete a backup, restore a backup, or upgrade Core or the OS
16
16
 
17
17
  SYMPTOMS:
18
18
  - Agent uses Jinja2 templates where native options exist
@@ -22,9 +22,10 @@ description: >
22
22
  - Agent hard-codes values or uses raw sensor over helper
23
23
  - Agent edits .storage, writes YAML, or generates YAML snippets
24
24
  - Agent tells user to edit configuration.yaml for UI integrations
25
- - Agent hardcodes entities in a Blueprint or uses free-text input over a selector
25
+ - Agent hardcodes Blueprint entities or uses free-text over a selector
26
+ - Agent changes existing state with no recovery path
26
27
  metadata:
27
- version: 15
28
+ version: 18
28
29
  ---
29
30
 
30
31
  # Home Assistant Best Practices
@@ -122,6 +123,10 @@ See `references/device-control.md#zigbee-buttonremote-patterns`.
122
123
  | Free-text input for an entity/device in a Blueprint | Typed `entity`/`target`/`device` selector | Text lets typos through and fails silently; selectors validate the choice | `references/blueprint-guide.md#inputs-and-selectors` |
123
124
  | `!input` used directly inside a template | Bind it to a `variables:` entry, use the variable | `!input` is a YAML tag, not a template value — the template errors or ignores it | `references/blueprint-guide.md#referencing-inputs-input-and-templating` |
124
125
  | Publishing a Blueprint without `source_url` | Set `source_url` to the file's canonical URL | Without it users can't re-import updates and sharing is awkward | `references/blueprint-guide.md#blueprint-metadata` |
126
+ | Full instance restore to undo one automation/script/scene/dashboard/helper edit | Roll that object back — write its previous definition back through the config API | A full restore reverts every unrelated change made since the backup and restarts HA | `references/backups.md#which-recovery-path` |
127
+ | Deleting a device/entity/integration, or upgrading Core, without a preceding backup | Take the full backup *before* the operation | Registry deletions can't be undone by writing the old value back, and a backup taken afterward captures the damage | `references/backups.md#when-a-full-backup-earns-its-cost` |
128
+ | Restoring a backup, deleting a backup, or upgrading Core or the OS without explicit user confirmation | Ask, name the concrete effect, and wait for an answer — every time, backup or not | A full restore discards everything since the archive for all restored parts and restarts HA; a Supervisor partial restore overwrites only the selected archive parts; deletion destroys a recovery point; a Core/OS upgrade is high-impact and its recovery path IS the pre-upgrade backup | `references/backups.md` |
129
+ | Calling a service "reversible" without naming its inverse | Treat an operation as reversible only when you can name and target its exact inverse | `vacuum.send_command`, `*.press` and `notify.*` have none; and an exact inverse still doesn't undo the consequence — re-locking a door doesn't un-expose the house | `references/backups.md#when-a-full-backup-earns-its-cost` |
125
130
 
126
131
  ---
127
132
 
@@ -144,3 +149,4 @@ Read these when you need detailed information:
144
149
  | `references/examples.yaml` | Need compound examples combining multiple best practices | — |
145
150
  | `references/appdaemon.md` | AppDaemon apps: when to use vs. native HA, app structure, service calls, scheduling, error handling, safe refactoring impact | — |
146
151
  | `references/blueprint-guide.md` | Authoring reusable blueprints: metadata & `source_url`, inputs & selectors, `target` vs `entity`, defaults, input sections, `!input` templating, versioning | `#when-to-author-a-blueprint`, `#blueprint-metadata`, `#inputs-and-selectors`, `#target-selector-vs-entity-selector`, `#defaults`, `#input-sections`, `#referencing-inputs-input-and-templating`, `#versioning-and-updates`, `#common-pitfalls` |
152
+ | `references/backups.md` | Deciding whether an operation needs a backup first; choosing between a full restore, a partial restore, and rolling one object back; what an archive actually contains; encryption keys and the emergency kit; restore verification; what HA does and does not protect when deleting a backup | `#which-recovery-path`, `#when-a-full-backup-earns-its-cost`, `#what-a-full-backup-contains`, `#restoring-consequences-and-verification`, `#deleting-backups`, `#common-pitfalls` |
@@ -927,6 +927,36 @@ variables:
927
927
 
928
928
  `trigger_variables:` is a separate top-level key evaluated **before** triggers fire — it supports **limited templates only** (no `states()`/`state_attr()`), mainly for passing a blueprint `!input` into trigger options. Don't put state-based templates there.
929
929
 
930
+ ### Keys render in order, one at a time
931
+
932
+ A `variables:` block renders one key at a time, each rendered result feeding the context for the next. A key that reads a name declared **further down the same block** reads it while it is still undefined.
933
+
934
+ ```yaml
935
+ # AVOID — `total` is still undefined when `msg` renders
936
+ variables:
937
+ msg: "Total: {{ total }}"
938
+ total: "{{ states('sensor.a') | float(0) + states('sensor.b') | float(0) }}"
939
+
940
+ # CORRECT — declare before use
941
+ variables:
942
+ total: "{{ states('sensor.a') | float(0) + states('sensor.b') | float(0) }}"
943
+ msg: "Total: {{ total }}"
944
+ ```
945
+
946
+ What the forward read costs depends on how the template uses the name (behavior as of 2026.7):
947
+
948
+ | Use of the undefined name | Result |
949
+ |---|---|
950
+ | `x.attr`, `x['k']`, arithmetic, `<`/`>`/`<=`/`>=`, `\| int`, `\| float` | Raises `UndefinedError`, logs ERROR — **aborts the run** |
951
+ | `{{ x }}`, `{% if x %}`, `{% for i in x %}`, `'p' ~ x` | Empty and falsy, logs WARNING — runs on into the else branch |
952
+ | `x \| length`, `x == y`, `x != y` | `0` / `False` / `True` — **no log and no trace entry** |
953
+
954
+ For the aborting row, [`continue_on_error:`](#continue-on-error) is no escape — it is an action option, a top-level block is not an action, and on a mid-sequence step it cannot suppress a render error. A default like `| int(0)` does not save it either: the default covers an unconvertible value, not an undefined name. In the silent row, watch `!=`: `{% if later != 'x' %}` on a name that is not defined yet takes the **then** branch, leaving nothing behind to explain why.
955
+
956
+ Not a problem when the name also exists in an enclosing scope — the reference then resolves outward to that value. The two placements diverge on that collision, though: a top-level `variables:` block **skips** a key already present in the incoming scope, so the incoming value stands and the block's own definition never takes effect for that run, while a mid-sequence `- variables:` step renders and **overwrites** it. Staging a computation across consecutive `variables:` steps is therefore mid-sequence only.
957
+
958
+ When an automation declares both `trigger_variables:` and `variables:`, the two render as one sequential mapping with `trigger_variables:` first, so a `variables:` key may read a `trigger_variables:` key but not the reverse. That holds for names declared in only one of the two: a name in both merges into a single entry that keeps the `trigger_variables:` position but takes the `variables:` template, so it renders early — before any `trigger_variables:` key declared after it. The separate attach-time render noted above happens before any `variables:` key exists.
959
+
930
960
  ---
931
961
 
932
962
  ## Capturing Action Responses