ha-mcp-dev 8.4.3.dev2542__tar.gz → 8.4.3.dev2546__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 (308) hide show
  1. {ha_mcp_dev-8.4.3.dev2542/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.4.3.dev2546}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/backup_manager.py +54 -6
  4. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/config.py +5 -5
  5. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/en.json +2 -2
  6. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/settings.js +1 -1
  7. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/log_common.py +4 -4
  8. ha_mcp_dev-8.4.3.dev2546/src/ha_mcp/tools/log_sources_fault.py +295 -0
  9. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_filesystem.py +4 -2
  10. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_logs.py +29 -9
  11. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  12. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  13. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/LICENSE +0 -0
  14. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/MANIFEST.in +0 -0
  15. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/README.md +0 -0
  16. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/setup.cfg +0 -0
  17. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/__init__.py +0 -0
  18. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/__main__.py +0 -0
  19. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_pypi_marker +0 -0
  20. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_rich_compat.py +0 -0
  21. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/__init__.py +0 -0
  22. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/requirements.txt +0 -0
  23. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  24. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  25. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  26. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  27. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  28. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  29. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  30. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  31. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  32. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  33. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  34. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  35. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  36. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  37. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  38. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  39. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  40. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  41. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  42. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  43. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  44. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  45. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  46. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  47. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  48. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  49. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  50. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  51. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  52. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  53. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  54. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  55. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  56. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  57. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  58. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  59. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  60. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  61. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  62. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  63. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  64. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  65. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  66. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  67. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  68. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  69. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  70. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  71. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  72. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  73. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  74. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  75. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  76. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  77. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  78. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  79. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_version.py +0 -0
  80. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/auth/__init__.py +0 -0
  81. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/auth/consent_form.py +0 -0
  82. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/auth/oidc_compat.py +0 -0
  83. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/auth/provider.py +0 -0
  84. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/browser_landing.py +0 -0
  85. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/__init__.py +0 -0
  86. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/rest_client.py +0 -0
  87. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/supervisor_client.py +0 -0
  88. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/websocket_client.py +0 -0
  89. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/websocket_listener.py +0 -0
  90. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  91. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  92. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  93. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  94. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  95. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  96. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/errors.py +0 -0
  97. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  98. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/llm_exposure.py +0 -0
  99. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/log_filters.py +0 -0
  100. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/__init__.py +0 -0
  101. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/approval_queue.py +0 -0
  102. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/editing.py +0 -0
  103. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/evaluator.py +0 -0
  104. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/handlers.py +0 -0
  105. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/middleware.py +0 -0
  106. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/model.py +0 -0
  107. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/persistence.py +0 -0
  108. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/value_sources.py +0 -0
  109. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/py.typed +0 -0
  110. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/read_only.py +0 -0
  111. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/redaction.py +0 -0
  112. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/renamed_tools.py +0 -0
  113. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  114. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  115. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  116. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  117. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  118. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  119. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  120. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  121. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  122. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  123. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/area-target-over-entity-list/case.yaml +0 -0
  124. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/arrive-home-automation/case.yaml +0 -0
  125. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/assist-shortcut-card/case.yaml +0 -0
  126. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/avg-temperature/case.yaml +0 -0
  127. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/backup-deletion-confirmation/case.yaml +0 -0
  128. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/blueprint-authoring/case.yaml +0 -0
  129. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/button-event-entity/case.yaml +0 -0
  130. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/device-id-trigger-migration/case.yaml +0 -0
  131. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/helper-flow-fields-not-platform-yaml/case.yaml +0 -0
  132. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/media-player-song/case.yaml +0 -0
  133. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/motion-light-automation-mode/case.yaml +0 -0
  134. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/rename-entity-impact-analysis/case.yaml +0 -0
  135. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/template-attributes-yaml-exception/case.yaml +0 -0
  136. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/timer-finished-automation/case.yaml +0 -0
  137. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/evals/vacuum-last-clean/case.yaml +0 -0
  138. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/scripts/check_eval_cases.py +0 -0
  139. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  140. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  141. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  142. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/backups.md +0 -0
  143. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  144. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  145. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  146. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  147. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  148. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  149. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  150. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  151. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  152. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  153. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  154. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/server.py +0 -0
  155. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/__init__.py +0 -0
  156. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  157. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  158. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  159. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  160. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  161. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  162. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  163. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
  164. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  165. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  166. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_theme.py +0 -0
  167. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  168. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  169. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  170. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  171. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  172. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  173. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  174. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  175. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
  176. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  177. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  178. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  179. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  180. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
  181. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  182. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/settings.css +0 -0
  183. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/settings.html +0 -0
  184. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/smoke_test.py +0 -0
  185. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  186. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/strict_bps.py +0 -0
  187. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/__init__.py +0 -0
  188. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/auto_backup.py +0 -0
  189. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/backup.py +0 -0
  190. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  191. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/best_practice_result.py +0 -0
  192. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  193. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/blueprint_sources.py +0 -0
  194. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/blueprint_substitute.py +0 -0
  195. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/blueprint_write.py +0 -0
  196. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/bulk_selector.py +0 -0
  197. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_api.py +0 -0
  198. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_config_entries.py +0 -0
  199. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_config_reads.py +0 -0
  200. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_devices.py +0 -0
  201. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_registries.py +0 -0
  202. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  203. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  204. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  205. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  206. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  207. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  208. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  209. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  210. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/device_control.py +0 -0
  211. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  212. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/hacs_registration.py +0 -0
  213. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/helpers.py +0 -0
  214. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  215. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/log_sources.py +0 -0
  216. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/log_sources_supervisor.py +0 -0
  217. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/__init__.py +0 -0
  218. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/base.py +0 -0
  219. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/matter.py +0 -0
  220. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/thread.py +0 -0
  221. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  222. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/zwave.py +0 -0
  223. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/reference_validator.py +0 -0
  224. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/registry.py +0 -0
  225. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  226. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  227. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  228. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  229. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  230. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  231. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  232. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
  233. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  234. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  235. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  236. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  237. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_addons.py +0 -0
  238. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_areas.py +0 -0
  239. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  240. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  241. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  242. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_calendar.py +0 -0
  243. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_camera.py +0 -0
  244. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_categories.py +0 -0
  245. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_code.py +0 -0
  246. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  247. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  248. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  249. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  250. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  251. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  252. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_dev.py +0 -0
  253. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_energy.py +0 -0
  254. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_entities.py +0 -0
  255. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_groups.py +0 -0
  256. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_hacs.py +0 -0
  257. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_history.py +0 -0
  258. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_integrations.py +0 -0
  259. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_labels.py +0 -0
  260. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_radio.py +0 -0
  261. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_registry.py +0 -0
  262. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_resources.py +0 -0
  263. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_search.py +0 -0
  264. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  265. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_service.py +0 -0
  266. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_services.py +0 -0
  267. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_system.py +0 -0
  268. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_themes.py +0 -0
  269. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_todo.py +0 -0
  270. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_traces.py +0 -0
  271. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_updates.py +0 -0
  272. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_utility.py +0 -0
  273. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  274. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  275. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  276. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_zones.py +0 -0
  277. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/util_helpers.py +0 -0
  278. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/validation_middleware.py +0 -0
  279. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/transforms/__init__.py +0 -0
  280. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/transforms/categorized_search.py +0 -0
  281. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  282. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/transport_security.py +0 -0
  283. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/update_check.py +0 -0
  284. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/__init__.py +0 -0
  285. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/config_hash.py +0 -0
  286. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/config_write_lock.py +0 -0
  287. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/data_paths.py +0 -0
  288. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/device_registry_semantics.py +0 -0
  289. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/domain_handlers.py +0 -0
  290. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/entity_membership.py +0 -0
  291. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  292. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  293. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/operation_manager.py +0 -0
  294. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/python_sandbox.py +0 -0
  295. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/skill_loader.py +0 -0
  296. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/usage_logger.py +0 -0
  297. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/__init__.py +0 -0
  298. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/enforcement.py +0 -0
  299. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/model.py +0 -0
  300. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/persistence.py +0 -0
  301. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/resolver.py +0 -0
  302. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  303. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  304. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  305. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  306. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/tests/__init__.py +0 -0
  307. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/tests/test_constants.py +0 -0
  308. {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/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.4.3.dev2542
3
+ Version: 8.4.3.dev2546
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.4.3.dev2542"
7
+ version = "8.4.3.dev2546"
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
@@ -7,7 +7,11 @@ Storage path resolution
7
7
  -----------------------
8
8
  ``Settings.auto_backup_dir`` overrides; otherwise defaults to
9
9
  ``/data/ha_mcp_backups`` in the add-on (SUPERVISOR_TOKEN set + ``/data``
10
- exists), else ``${XDG_DATA_HOME:-~/.local/share}/ha_mcp/backups``.
10
+ exists), else ``<data dir>/backups`` where the data dir is what
11
+ ``utils.data_paths.get_data_dir`` resolves (``HA_MCP_CONFIG_DIR``,
12
+ ``~/.ha-mcp``, or the tmpdir fallback). Installs that still hold snapshots
13
+ under the pre-#2372 default ``${XDG_DATA_HOME:-~/.local/share}/ha_mcp/backups``
14
+ keep using that directory; see ``_resolve_default_dir``.
11
15
 
12
16
  File format
13
17
  -----------
@@ -55,6 +59,7 @@ import yaml # type: ignore[import-untyped]
55
59
  from fastmcp.exceptions import ToolError
56
60
 
57
61
  from .client.rest_client import HomeAssistantConnectionError, HomeAssistantError
62
+ from .utils.data_paths import get_data_dir
58
63
 
59
64
  logger = logging.getLogger(__name__)
60
65
 
@@ -263,17 +268,60 @@ def _now_iso() -> str:
263
268
  return datetime.now(UTC).isoformat()
264
269
 
265
270
 
266
- def _resolve_default_dir() -> Path:
267
- """Pick a sane default backup directory for the current deployment mode."""
268
- # Addon detection: Supervisor sets SUPERVISOR_TOKEN AND /data exists.
269
- if os.environ.get("SUPERVISOR_TOKEN") and Path("/data").is_dir():
270
- return Path("/data/ha_mcp_backups")
271
+ # Add-on default. Named in the add-on option descriptions, so it stays put.
272
+ _ADDON_BACKUP_DIR = Path("/data/ha_mcp_backups")
273
+
274
+
275
+ def _legacy_default_dir() -> Path:
276
+ """The non-add-on default before #2372: XDG user-data, outside the data dir."""
271
277
  xdg = os.environ.get("XDG_DATA_HOME")
272
278
  if xdg:
273
279
  return Path(xdg) / "ha_mcp" / "backups"
274
280
  return (Path.home() / ".local" / "share" / "ha_mcp" / "backups").resolve()
275
281
 
276
282
 
283
+ def _holds_files(path: Path) -> bool:
284
+ """Whether ``path`` is a directory with at least one entry.
285
+
286
+ An unreadable or missing directory counts as empty: nothing in it can be
287
+ listed or restored from, so there is nothing to keep using.
288
+ """
289
+ try:
290
+ return any(path.iterdir())
291
+ except OSError:
292
+ return False
293
+
294
+
295
+ def _resolve_default_dir() -> Path:
296
+ """Pick a sane default backup directory for the current deployment mode.
297
+
298
+ Add-on (Supervisor sets ``SUPERVISOR_TOKEN`` and ``/data`` exists):
299
+ ``/data/ha_mcp_backups``. Otherwise ``backups/`` inside the data dir, the
300
+ directory that already holds the settings and that every documented
301
+ Docker recipe persists as the ``ha-mcp-data`` volume. The previous default,
302
+ ``${XDG_DATA_HOME:-~/.local/share}/ha_mcp/backups``, sat outside that
303
+ volume: under a ``read_only`` root it was EROFS, which fails the mandatory
304
+ pre-write snapshots closed, and on a writable root the snapshots were
305
+ discarded with the container (#2372).
306
+
307
+ An install that already holds snapshots under the previous default keeps
308
+ using it so its restore points stay listed and rotation keeps counting
309
+ them; only installs with nothing there move to the data dir.
310
+ """
311
+ if os.environ.get("SUPERVISOR_TOKEN") and _ADDON_BACKUP_DIR.parent.is_dir():
312
+ return _ADDON_BACKUP_DIR
313
+ legacy = _legacy_default_dir()
314
+ if _holds_files(legacy):
315
+ logger.info(
316
+ "Auto-backup: keeping existing backup dir %s (new installs default "
317
+ "to %s; set HAMCP_BACKUP_DIR to choose explicitly)",
318
+ legacy,
319
+ get_data_dir() / "backups",
320
+ )
321
+ return legacy
322
+ return get_data_dir() / "backups"
323
+
324
+
277
325
  # Sentinel returned by ``BackupManager._fetch_config_for_snapshot`` when there
278
326
  # is nothing to snapshot — a handled transient fetch failure (already logged),
279
327
  # or a None config for an entity that did not exist. Distinct from any real
@@ -400,7 +400,7 @@ class Settings(BaseSettings):
400
400
 
401
401
  # Backup directory override. Empty ("") resolves at runtime to a
402
402
  # deployment-mode default: ``/data/ha_mcp_backups`` in the add-on,
403
- # otherwise ``${XDG_DATA_HOME:-~/.local/share}/ha_mcp/backups``.
403
+ # otherwise ``<data dir>/backups`` (see ``backup_manager._resolve_default_dir``).
404
404
  auto_backup_dir: str = Field("", alias="HAMCP_BACKUP_DIR")
405
405
 
406
406
  # Calendar event backups query an ahead-of-now window to locate the
@@ -776,8 +776,8 @@ FEATURE_FLAG_FIELDS: tuple[FeatureFlagField, ...] = (
776
776
  )
777
777
 
778
778
  # Override-file location is the same data dir that holds tool_config.json
779
- # (resolved via ``utils.data_paths.get_data_dir`` — addon ``/data``,
780
- # ``HA_MCP_CONFIG_DIR``, ``XDG_DATA_HOME``, or a tmpdir fallback).
779
+ # (resolved via ``utils.data_paths.get_data_dir`` — ``HA_MCP_CONFIG_DIR``,
780
+ # addon ``/data``, ``~/.ha-mcp``, or a tmpdir fallback).
781
781
  # Imported lazily inside helpers to avoid a circular import at module
782
782
  # load.
783
783
  _FEATURE_FLAG_OVERRIDE_FILENAME = "feature_flags.json"
@@ -1656,8 +1656,8 @@ BACKUP_OVERRIDE_FIELDS: tuple[BackupOverrideField, ...] = (
1656
1656
  )
1657
1657
 
1658
1658
  # Override-file location is the same data dir that holds tool_config.json
1659
- # (resolved via ``utils.data_paths.get_data_dir`` — addon ``/data``,
1660
- # ``HA_MCP_CONFIG_DIR``, ``XDG_DATA_HOME``, or a tmpdir fallback).
1659
+ # (resolved via ``utils.data_paths.get_data_dir`` — ``HA_MCP_CONFIG_DIR``,
1660
+ # addon ``/data``, ``~/.ha-mcp``, or a tmpdir fallback).
1661
1661
  # Imported lazily inside helpers to avoid a circular import at module
1662
1662
  # load (``utils.data_paths`` imports from ``_version`` which imports
1663
1663
  # from ``config`` transitively in some test layouts).
@@ -251,7 +251,7 @@
251
251
  "backup.fields.auto_backup_retain_per_entity.label": "Retain per entity",
252
252
  "backup.fields.auto_backup_retain_per_entity.help": "Maximum snapshots kept per entity (1–10000). Older ones rotate out.",
253
253
  "backup.fields.auto_backup_dir.label": "Backup directory override",
254
- "backup.fields.auto_backup_dir.help": "Leave empty to use the default directory; otherwise enter an absolute path.",
254
+ "backup.fields.auto_backup_dir.help": "Leave empty for the default: /data/ha_mcp_backups in the App (add-on), otherwise the backups/ subdirectory of the ha-mcp data directory; an install that already holds snapshots under the earlier default keeps using it. The directory in use is shown in the backup status. Or enter an absolute path.",
255
255
  "backup.fields.auto_backup_calendar_lookahead_days.label": "Calendar lookahead (days)",
256
256
  "backup.fields.auto_backup_calendar_lookahead_days.help": "How far ahead to query calendar events for pre-edit snapshots. Range 1–365.",
257
257
  "backup.fields.enable_snapshot_delete.label": "Allow snapshot deletion",
@@ -288,7 +288,7 @@
288
288
  "backup.bulk.confirm": "Delete all backups matching: {filters}?",
289
289
  "backup.bulk.deleted": "Deleted {count} backup(s)",
290
290
  "filesystem.custom.title": "Custom filesystem paths (advanced)",
291
- "filesystem.custom.help": "Extra paths (one per line) that file tools may <strong>read and write</strong>. Config-relative paths, including exact filenames such as <code>sensor.yaml</code>, and paths under <code>/share</code>, <code>/media</code>, <code>/ssl</code>, and <code>/backup</code> are supported. Each entry allows that path and anything below it. You can also manage these paths in Home Assistant under Settings → Devices & Services → HA-MCP Custom Component → HA-MCP File & YAML Tools → Configure; both locations edit the same setting and apply changes immediately.",
291
+ "filesystem.custom.help": "Extra paths (one per line) that file tools may <strong>read and write</strong>. Config-relative paths, including exact filenames such as <code>sensor.yaml</code>, and paths under <code>/share</code>, <code>/media</code>, <code>/ssl</code>, and <code>/backup</code> are supported. Each entry allows that path and anything below it. Entries are matched literally; wildcards such as <code>*</code> are not supported. You can also manage these paths in Home Assistant under Settings → Devices & Services → HA-MCP Custom Component → HA-MCP File & YAML Tools → Configure; both locations edit the same setting and apply changes immediately.",
292
292
  "filesystem.custom.blocked": "Always blocked: {paths}, path traversal (<code>..</code>), and absolute paths outside allowed HAOS volumes.",
293
293
  "filesystem.custom.enable_first": "Enable beta features and filesystem tools above to edit.",
294
294
  "filesystem.custom.unavailable": "Custom paths are currently unavailable.",
@@ -1438,7 +1438,7 @@ const BACKUP_FIELD_LABELS = {
1438
1438
  },
1439
1439
  auto_backup_dir: {
1440
1440
  label: 'Backup directory override',
1441
- help: 'Empty = default (/data/ha_mcp_backups in the App (add-on), $XDG_DATA_HOME/ha_mcp/backups otherwise). Override with an absolute path.',
1441
+ help: 'Leave empty for the default: /data/ha_mcp_backups in the App (add-on), otherwise the backups/ subdirectory of the ha-mcp data directory; an install that already holds snapshots under the earlier default keeps using it. The directory in use is shown in the backup status. Or enter an absolute path.',
1442
1442
  },
1443
1443
  auto_backup_calendar_lookahead_days: {
1444
1444
  label: 'Calendar lookahead (days)',
@@ -128,7 +128,7 @@ def _collect_log_warnings(
128
128
  f"ignored for source='{source}'"
129
129
  )
130
130
  if (
131
- source in ("logbook", "logger", "supervisor", "system_service")
131
+ source in ("logbook", "logger", "supervisor", "system_service", "fault_log")
132
132
  and level is not None
133
133
  ):
134
134
  warnings.append(
@@ -140,10 +140,10 @@ def _collect_log_warnings(
140
140
  "Parameter 'slug' only applies to source='supervisor' or "
141
141
  f"'system_service'; ignored for source='{source}'"
142
142
  )
143
- if source not in ("logbook", "error_log") and offset:
143
+ if source not in ("logbook", "error_log", "fault_log") and offset:
144
144
  warnings.append(
145
- "Parameter 'offset' only applies to source='logbook' or "
146
- f"'error_log'; ignored for source='{source}'"
145
+ "Parameter 'offset' only applies to source='logbook', 'error_log' "
146
+ f"or 'fault_log'; ignored for source='{source}'"
147
147
  )
148
148
  return warnings
149
149
 
@@ -0,0 +1,295 @@
1
+ """``ha_get_logs(source='fault_log')`` — Home Assistant's native-crash dump.
2
+
3
+ HA Core's ``__main__`` enables :mod:`faulthandler` on ``home-assistant.log.fault``
4
+ in the config root. faulthandler writes only on a native fatal signal (SIGSEGV,
5
+ SIGABRT, SIGBUS, SIGILL, SIGFPE, or a Python fatal error): the process dies
6
+ before any logging runs, so the per-thread Python traceback it dumps reaches
7
+ neither journald nor ``home-assistant.log``. No other ``ha_get_logs`` source can
8
+ show it (issue #2373).
9
+
10
+ The file is opened in append mode on every start, so on a healthy install it
11
+ exists and is empty; each crash appends one block that opens with a
12
+ ``Fatal Python error: ...`` line followed by one traceback per thread. A block
13
+ is only readable in its original line order, so unlike the line-oriented
14
+ raw-text sources this one orders whole blocks and pages through the result
15
+ with ``offset`` instead of reversing lines.
16
+
17
+ The read goes through the File & YAML Tools entry's privileged ``read_file``
18
+ service (the same route as ``ha_read_file``), which allows the path from
19
+ component 2.1.4. Split out of ``log_sources`` under `.gemini/styleguide.md`
20
+ § Tool Consolidation and Module Size.
21
+ """
22
+
23
+ from typing import Any, Literal, NoReturn
24
+
25
+ from fastmcp.exceptions import ToolError
26
+
27
+ from ..client.rest_client import (
28
+ HomeAssistantAPIError,
29
+ HomeAssistantAuthError,
30
+ HomeAssistantConnectionError,
31
+ )
32
+ from ..errors import ErrorCode, create_error_response
33
+ from .helpers import exception_to_structured_error, raise_tool_error
34
+ from .log_common import (
35
+ DEFAULT_LOG_LIMIT,
36
+ SUPERVISOR_SEARCH_WINDOW_LINES,
37
+ _coerce_limit,
38
+ )
39
+ from .tools_filesystem import _assert_mcp_tools_available, call_mcp_tools_service
40
+ from .util_helpers import unwrap_service_response
41
+
42
+ # Config-relative path HA Core hands to ``faulthandler.enable`` (``FAULT_LOG_FILENAME``
43
+ # in ``homeassistant/__main__.py``).
44
+ FAULT_LOG_PATH = "home-assistant.log.fault"
45
+
46
+ # First component release whose read allowlist includes ``FAULT_LOG_PATH``. An
47
+ # older component answers the read with "Path not allowed"; that reply is the
48
+ # version signal, no separate probe needed.
49
+ MIN_COMPONENT_VERSION_FAULT_LOG = "2.1.4"
50
+
51
+ # Lines tailed from the file per call, independent of ``limit``. A single dump
52
+ # lists every thread, so it routinely runs past a 100-line ``limit``; tailing
53
+ # only ``limit`` lines would cut off the ``Fatal Python error`` header that
54
+ # names the signal. The window is the same one the other raw-text sources use
55
+ # for a search, and ``offset`` pages through it.
56
+ FAULT_LOG_WINDOW_LINES = SUPERVISOR_SEARCH_WINDOW_LINES
57
+
58
+ # faulthandler opens every dump with this line, so it delimits crash blocks.
59
+ _FATAL_MARKER = "Fatal Python error:"
60
+
61
+ _NO_CRASH_MESSAGE = (
62
+ "No native crash recorded: home-assistant.log.fault is empty. Ordinary "
63
+ "Python errors never land here; use source='system' or source='error_log' "
64
+ "for those."
65
+ )
66
+
67
+
68
+ def _is_missing_file_error(error: str) -> bool:
69
+ return "does not exist" in error or "not a file" in error
70
+
71
+
72
+ def _no_crash(data: dict[str, Any], total_lines: int) -> dict[str, Any]:
73
+ data.update(
74
+ crash_recorded=False,
75
+ log="",
76
+ total_lines=total_lines,
77
+ returned_lines=0,
78
+ has_more=False,
79
+ message=_NO_CRASH_MESSAGE,
80
+ )
81
+ return data
82
+
83
+
84
+ def _raise_read_failure(error: str) -> NoReturn:
85
+ """Turn a ``read_file`` refusal into the matching structured error.
86
+
87
+ "Path not allowed" is the one refusal with a known cause: a component
88
+ older than :data:`MIN_COMPONENT_VERSION_FAULT_LOG`, whose allowlist does
89
+ not yet carry the file. Anything else is reported verbatim.
90
+ """
91
+ if "Path not allowed" in error:
92
+ raise_tool_error(
93
+ create_error_response(
94
+ ErrorCode.COMPONENT_NOT_INSTALLED,
95
+ "The installed ha_mcp_tools custom component does not allow "
96
+ f"reading {FAULT_LOG_PATH} (requires component >= "
97
+ f"{MIN_COMPONENT_VERSION_FAULT_LOG}).",
98
+ details=error,
99
+ suggestions=[
100
+ "HACS → Integrations → HA-MCP Custom Component → Update",
101
+ "Restart Home Assistant after the update completes",
102
+ ],
103
+ context={"source": "fault_log"},
104
+ )
105
+ )
106
+ raise_tool_error(
107
+ create_error_response(
108
+ ErrorCode.SERVICE_CALL_FAILED,
109
+ f"read_file failed for {FAULT_LOG_PATH}: {error or 'unknown error'}",
110
+ # Parenthesised so each reads as one suggestion rather than a
111
+ # list entry with a missing comma (py/implicit-string-
112
+ # concatenation-in-list).
113
+ suggestions=[
114
+ (
115
+ f"Check that {FAULT_LOG_PATH} in the Home Assistant config "
116
+ "directory is readable by Home Assistant"
117
+ ),
118
+ "Check Home Assistant logs for the ha_mcp_tools read error",
119
+ (
120
+ "Retry after the file is readable; an absent or empty file "
121
+ "is reported as no crash, not as an error"
122
+ ),
123
+ ],
124
+ context={"source": "fault_log"},
125
+ )
126
+ )
127
+
128
+
129
+ def _split_blocks(lines: list[str]) -> list[list[str]]:
130
+ """Group the window into crash blocks, each opening with the fatal marker.
131
+
132
+ Lines before the first marker are the tail of a block whose header fell
133
+ outside the window; they form a leading partial block so nothing is lost.
134
+ """
135
+ blocks: list[list[str]] = []
136
+ current: list[str] = []
137
+ for line in lines:
138
+ if line.startswith(_FATAL_MARKER) and current:
139
+ blocks.append(current)
140
+ current = []
141
+ current.append(line)
142
+ if current:
143
+ blocks.append(current)
144
+ return blocks
145
+
146
+
147
+ def _shape_crash_page(
148
+ data: dict[str, Any],
149
+ lines: list[str],
150
+ *,
151
+ limit: int,
152
+ offset: int,
153
+ search: str | None,
154
+ order: Literal["newest", "oldest"],
155
+ ) -> dict[str, Any]:
156
+ """Order whole crash blocks, then page through the flattened text.
157
+
158
+ ``order='newest'`` puts the latest block first; every block keeps its own
159
+ line order so the traceback stays readable. ``search`` selects whole
160
+ blocks (any line matching) rather than lines, so a hit inside a traceback
161
+ still comes back with its ``Fatal Python error`` header and the rest of
162
+ the frames. ``offset``/``limit`` slice the assembled text from its start,
163
+ and ``has_more``/``next_offset`` continue it.
164
+ """
165
+ blocks = _split_blocks(lines)
166
+ fatal_blocks = sum(1 for block in blocks if block[0].startswith(_FATAL_MARKER))
167
+ if order == "newest":
168
+ blocks.reverse()
169
+
170
+ filters_applied: dict[str, str] = {}
171
+ if search:
172
+ search_lower = search.lower()
173
+ blocks = [b for b in blocks if any(search_lower in ln.lower() for ln in b)]
174
+ filters_applied["search"] = search
175
+ matched_blocks = len(blocks)
176
+ flat = [line for block in blocks for line in block]
177
+
178
+ page = flat[offset : offset + limit]
179
+ has_more = offset + limit < len(flat)
180
+ data.update(
181
+ crash_recorded=True,
182
+ log="\n".join(page),
183
+ returned_lines=len(page),
184
+ fatal_error_blocks_in_window=fatal_blocks,
185
+ has_more=has_more,
186
+ )
187
+ if has_more:
188
+ data["next_offset"] = offset + limit
189
+ # Carry the active filter: the next page must slice the same
190
+ # filtered block list, or offset lands in unrelated blocks.
191
+ search_arg = f", search={search!r}" if search else ""
192
+ data["pagination_hint"] = (
193
+ f"ha_get_logs(source='fault_log', offset={offset + limit}, "
194
+ f"limit={limit}, order='{order}'{search_arg})"
195
+ )
196
+ if filters_applied:
197
+ data["filters_applied"] = filters_applied
198
+ data["matched_blocks"] = matched_blocks
199
+ return data
200
+
201
+
202
+ class FaultLogSourceMixin:
203
+ """``fault_log`` source, mixed into ``LogTools``."""
204
+
205
+ _client: Any
206
+
207
+ async def _read_fault_file(self) -> dict[str, Any]:
208
+ """One gated ``read_file`` call, unwrapped; transport errors become ToolErrors.
209
+
210
+ The live tools-entry probe runs first, as the file and YAML tools do,
211
+ so an entry removed after this client cached its caller token surfaces
212
+ the actionable "add the entry" error rather than HA's 400 for a
213
+ service that no longer exists. That gate and the caller-token gate
214
+ raise their own ToolErrors, which pass through untouched.
215
+ """
216
+ try:
217
+ await _assert_mcp_tools_available(self._client)
218
+ raw = await call_mcp_tools_service(
219
+ self._client,
220
+ "read_file",
221
+ {"path": FAULT_LOG_PATH, "tail_lines": FAULT_LOG_WINDOW_LINES},
222
+ )
223
+ except ToolError:
224
+ raise
225
+ except (HomeAssistantAuthError, HomeAssistantAPIError) as e:
226
+ exception_to_structured_error(e, context={"source": "fault_log"})
227
+ except (HomeAssistantConnectionError, TimeoutError, OSError) as e:
228
+ exception_to_structured_error(
229
+ e,
230
+ context={"source": "fault_log"},
231
+ suggestions=["Check Home Assistant connection"],
232
+ )
233
+ return unwrap_service_response(raw) if isinstance(raw, dict) else {}
234
+
235
+ async def _get_fault_log(
236
+ self,
237
+ limit: int | None = None,
238
+ search: str | None = None,
239
+ offset: int = 0,
240
+ order: Literal["newest", "oldest"] = "newest",
241
+ ) -> dict[str, Any]:
242
+ """Read ``home-assistant.log.fault`` through the component's read_file.
243
+
244
+ Always tails :data:`FAULT_LOG_WINDOW_LINES` from the file; ``order``
245
+ arranges whole crash blocks, ``search`` keeps the blocks that mention
246
+ the term, and ``offset``/``limit`` page through the assembled text. An absent or
247
+ empty file is the healthy state and returns success with
248
+ ``crash_recorded=False`` rather than an error.
249
+ """
250
+ effective_limit = _coerce_limit(
251
+ limit, default=DEFAULT_LOG_LIMIT, suggestion_example="100"
252
+ )
253
+ result = await self._read_fault_file()
254
+
255
+ data: dict[str, Any] = {
256
+ "success": True,
257
+ "source": "fault_log",
258
+ "path": FAULT_LOG_PATH,
259
+ "limit": effective_limit,
260
+ "offset": offset,
261
+ "order": order,
262
+ "window_lines": FAULT_LOG_WINDOW_LINES,
263
+ }
264
+ if not result.get("success", False):
265
+ error = str(result.get("error", ""))
266
+ if _is_missing_file_error(error):
267
+ # HA opens the file at every start, so a missing file means
268
+ # this config dir has not been started by HA's __main__ (or
269
+ # someone removed it). Either way: nothing to show.
270
+ return _no_crash(data, total_lines=0)
271
+ _raise_read_failure(error)
272
+
273
+ content = result.get("content")
274
+ lines = content.splitlines() if isinstance(content, str) else []
275
+ # The component reports the untailed line count; fall back to what we
276
+ # got when an older shape omits it.
277
+ total_lines = result.get("total_lines")
278
+ if not isinstance(total_lines, int):
279
+ total_lines = len(lines)
280
+ if isinstance(result.get("modified"), str):
281
+ data["modified"] = result["modified"]
282
+
283
+ if not any(line.strip() for line in lines):
284
+ return _no_crash(data, total_lines=total_lines)
285
+ data["total_lines"] = total_lines
286
+ # Older blocks beyond the window are unreachable by offset; say so.
287
+ data["window_truncated"] = bool(result.get("truncated", False))
288
+ return _shape_crash_page(
289
+ data,
290
+ lines,
291
+ limit=effective_limit,
292
+ offset=offset,
293
+ search=search,
294
+ order=order,
295
+ )
@@ -712,13 +712,15 @@ class FilesystemTools:
712
712
  Reads files from allowed paths within the config directory. Some files
713
713
  have special handling:
714
714
  - `secrets.yaml`: Values are masked for security
715
- - `home-assistant.log`: Limited to tail (last N lines) by default
715
+ - `home-assistant.log` / `home-assistant.log.fault`: Limited to tail
716
+ (last N lines) by default. Prefer ha_get_logs(source='error_log') and
717
+ ha_get_logs(source='fault_log') over reading these directly.
716
718
 
717
719
  **Allowed Read Paths:**
718
720
  - `configuration.yaml`, `automations.yaml`, `scripts.yaml`, `scenes.yaml`
719
721
  - `secrets.yaml` (values masked)
720
722
  - `packages/*.yaml`
721
- - `home-assistant.log` (tail only)
723
+ - `home-assistant.log`, `home-assistant.log.fault` (tail only)
722
724
  - `www/**`, `themes/**`, `custom_templates/**`, `dashboards/**`, `blueprints/**`
723
725
  - `custom_components/**/*.py` (read-only)
724
726
  - Plus any custom directories OR HAOS sibling volumes (`/share`,
@@ -22,10 +22,11 @@ from .log_common import (
22
22
  _validate_log_slug,
23
23
  )
24
24
  from .log_sources import CoreLogSourcesMixin
25
+ from .log_sources_fault import FaultLogSourceMixin
25
26
  from .log_sources_supervisor import SupervisorLogSourcesMixin
26
27
 
27
28
 
28
- class LogTools(CoreLogSourcesMixin, SupervisorLogSourcesMixin):
29
+ class LogTools(CoreLogSourcesMixin, SupervisorLogSourcesMixin, FaultLogSourceMixin):
29
30
  """Dispatches ``ha_get_logs`` to one log source and shapes the response."""
30
31
 
31
32
  def __init__(self, client: Any) -> None:
@@ -76,6 +77,10 @@ class LogTools(CoreLogSourcesMixin, SupervisorLogSourcesMixin):
76
77
  # logger reports per-integration levels, not time-ordered events;
77
78
  # 'order' does not apply (a warning is emitted upstream).
78
79
  return await self._get_logger_info(limit=limit, search=search)
80
+ if source == "fault_log":
81
+ return await self._get_fault_log(
82
+ limit=limit, search=search, offset=offset, order=order
83
+ )
79
84
  if source == "system_service":
80
85
  assert slug is not None # guaranteed by _validate_log_slug
81
86
  return await self._get_system_service_log(
@@ -171,6 +176,7 @@ def register_logs_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
171
176
  "supervisor",
172
177
  "system_service",
173
178
  "logger",
179
+ "fault_log",
174
180
  ] = "logbook",
175
181
  # Shared parameters
176
182
  limit: int | None = None,
@@ -180,7 +186,8 @@ def register_logs_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
180
186
  Field(
181
187
  description=(
182
188
  "Sort order for time-ordered sources (logbook, system, "
183
- "error_log, supervisor, system_service): 'newest' (default) "
189
+ "error_log, supervisor, system_service, fault_log): "
190
+ "'newest' (default) "
184
191
  "returns most-recent first; 'oldest' returns chronological-"
185
192
  "first. Ignored for source='logger', and for "
186
193
  "source='error_log' with structured=True (that summary is "
@@ -197,10 +204,12 @@ def register_logs_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
197
204
  Field(
198
205
  ge=0,
199
206
  description=(
200
- "Page deeper into source='logbook' and source='error_log' "
201
- "(ignored for other sources). On error_log it counts raw "
202
- "log lines back from the newest entry; pass the response's "
203
- "'next_offset' to continue while 'has_more' is true."
207
+ "Page deeper into source='logbook', 'error_log' and "
208
+ "'fault_log' (ignored for other sources). On error_log it "
209
+ "counts raw log lines back from the newest entry; on "
210
+ "fault_log it counts lines from the start of the assembled "
211
+ "crash text. Pass the response's 'next_offset' to continue "
212
+ "while 'has_more' is true."
204
213
  ),
205
214
  ),
206
215
  ] = 0,
@@ -245,6 +254,15 @@ def register_logs_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
245
254
  - "system_service": HA-Supervisor-managed system service logs (requires
246
255
  slug ∈ {supervisor, host, core, dns, audio, cli, multicast, observer})
247
256
  - "logger": Effective log level per integration via logger/log_info (confirms logger.set_level changes took effect)
257
+ - "fault_log": HA Core's faulthandler crash dump (home-assistant.log.fault).
258
+ Written only when HA dies from a native fatal signal (segfault, abort,
259
+ Python fatal error), which never reaches journald or error_log. Empty
260
+ on a healthy install (crash_recorded=False). Whole crash blocks are
261
+ ordered (newest first by default) with each block's lines kept in
262
+ place so the traceback reads correctly; search keeps every block
263
+ that mentions the term; offset/limit page through the assembled
264
+ text. Reads through the "HA-MCP File & YAML Tools"
265
+ entry (component >= 2.1.4).
248
266
 
249
267
  **Prefer source='system' for triage.** It returns HA's own deduplicated
250
268
  system_log entries with counts, first_occurred and full tracebacks; of
@@ -258,10 +276,12 @@ def register_logs_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
258
276
  cap, or for the per-component rollup.
259
277
 
260
278
  **Shared params:** limit, search (keyword filter on entries/lines; matches integration domain for source='logger')
261
- **Order:** order='newest' (default) returns most-recent first; order='oldest' returns chronological-first. Applies to all time-ordered sources (logbook, system, error_log, supervisor, system_service); ignored for source='logger' and for error_log with structured=True. For raw-text sources (error_log, supervisor, system_service) it sets the read direction of the most-recent window.
279
+ **Order:** order='newest' (default) returns most-recent first; order='oldest' returns chronological-first. Applies to all time-ordered sources (logbook, system, error_log, supervisor, system_service, fault_log); ignored for source='logger' and for error_log with structured=True. For raw-text sources (error_log, supervisor, system_service) it sets the read direction of the most-recent window; fault_log orders whole crash blocks instead of lines.
262
280
  **Logbook params:** hours_back, entity_id, end_time, compact (default True — strips attribute dicts to save context)
263
- **Pagination (logbook + error_log):** offset pages deeper; ignored for the
264
- other sources. Logbook responses carry has_more plus a
281
+ **Pagination (logbook + error_log + fault_log):** offset pages deeper; ignored for the
282
+ other sources. fault_log always reads a fixed window from the end of
283
+ the file, orders its crash blocks, and pages the assembled text
284
+ from the start with has_more/next_offset. Logbook responses carry has_more plus a
265
285
  pagination_hint. On error_log, offset counts raw log lines back from
266
286
  the newest entry (journald entries on Supervisor-backed installs),
267
287
  both modes read a bounded window per call — so `level`/`search`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 8.4.3.dev2542
3
+ Version: 8.4.3.dev2546
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
@@ -208,6 +208,7 @@ src/ha_mcp/tools/helpers.py
208
208
  src/ha_mcp/tools/integration_reconfigure.py
209
209
  src/ha_mcp/tools/log_common.py
210
210
  src/ha_mcp/tools/log_sources.py
211
+ src/ha_mcp/tools/log_sources_fault.py
211
212
  src/ha_mcp/tools/log_sources_supervisor.py
212
213
  src/ha_mcp/tools/reference_validator.py
213
214
  src/ha_mcp/tools/registry.py