ha-mcp-dev 8.2.0.dev2321__tar.gz → 8.2.0.dev2323__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 (267) hide show
  1. {ha_mcp_dev-8.2.0.dev2321/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.2.0.dev2323}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/client/rest_client.py +31 -12
  4. ha_mcp_dev-8.2.0.dev2323/src/ha_mcp/tools/error_log_parsing.py +365 -0
  5. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_utility.py +243 -48
  6. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  7. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  8. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/LICENSE +0 -0
  9. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/README.md +0 -0
  11. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/setup.cfg +0 -0
  12. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/__init__.py +0 -0
  13. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/__main__.py +0 -0
  14. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_pypi_marker +0 -0
  15. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_rich_compat.py +0 -0
  16. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/__init__.py +0 -0
  17. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/requirements.txt +0 -0
  18. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  19. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  20. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  21. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  22. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  23. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  24. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  25. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  26. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  27. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  28. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  29. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  30. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  31. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  32. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  33. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  34. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  35. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  36. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  37. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  38. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  39. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  40. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  41. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  42. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  43. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  44. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  45. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  46. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  47. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  48. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  49. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  50. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  51. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  52. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  53. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  54. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  55. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  56. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  57. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  58. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  59. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  60. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  61. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  62. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  63. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  64. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  65. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  66. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  67. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  68. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  69. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  70. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  71. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  72. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  73. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  74. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/_version.py +0 -0
  75. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/auth/__init__.py +0 -0
  76. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/auth/consent_form.py +0 -0
  77. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/auth/oidc_compat.py +0 -0
  78. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/auth/provider.py +0 -0
  79. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/backup_manager.py +0 -0
  80. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/browser_landing.py +0 -0
  81. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/client/__init__.py +0 -0
  82. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/client/supervisor_client.py +0 -0
  83. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/client/websocket_client.py +0 -0
  84. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/client/websocket_listener.py +0 -0
  85. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/config.py +0 -0
  86. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  87. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  88. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  89. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  90. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  91. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  92. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/errors.py +0 -0
  93. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  94. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/llm_exposure.py +0 -0
  95. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/__init__.py +0 -0
  96. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/approval_queue.py +0 -0
  97. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/editing.py +0 -0
  98. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/evaluator.py +0 -0
  99. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/handlers.py +0 -0
  100. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/middleware.py +0 -0
  101. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/model.py +0 -0
  102. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/persistence.py +0 -0
  103. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/policy/value_sources.py +0 -0
  104. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/py.typed +0 -0
  105. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/read_only.py +0 -0
  106. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/redaction.py +0 -0
  107. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  108. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  109. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  110. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  111. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  112. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  113. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  114. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  115. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  116. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  117. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  118. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  119. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  120. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  121. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  122. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  123. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  124. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  125. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  126. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  127. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  128. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  129. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  130. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  131. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  132. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/server.py +0 -0
  133. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/__init__.py +0 -0
  134. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  135. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  136. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  137. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  138. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  139. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  140. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  141. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  142. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  143. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_theme.py +0 -0
  144. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  145. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  146. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  147. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  148. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  149. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  150. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  151. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  152. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  153. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  154. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  155. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  156. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  157. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/settings.css +0 -0
  158. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/settings.html +0 -0
  159. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/settings_ui/settings.js +0 -0
  160. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/smoke_test.py +0 -0
  161. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  162. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/strict_bps.py +0 -0
  163. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/__init__.py +0 -0
  164. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/auto_backup.py +0 -0
  165. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/backup.py +0 -0
  166. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  167. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/best_practice_result.py +0 -0
  168. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  169. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/component_api.py +0 -0
  170. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/component_config_reads.py +0 -0
  171. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/component_devices.py +0 -0
  172. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/component_registries.py +0 -0
  173. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  174. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  175. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  176. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  177. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  178. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/device_control.py +0 -0
  179. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/hacs_registration.py +0 -0
  180. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/helpers.py +0 -0
  181. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/radio/__init__.py +0 -0
  182. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/radio/base.py +0 -0
  183. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/radio/matter.py +0 -0
  184. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/radio/thread.py +0 -0
  185. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  186. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/radio/zwave.py +0 -0
  187. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/reference_validator.py +0 -0
  188. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/registry.py +0 -0
  189. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  190. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  191. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  192. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  193. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  194. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  195. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  196. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  197. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  198. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  199. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_addons.py +0 -0
  200. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_areas.py +0 -0
  201. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  202. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  203. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_calendar.py +0 -0
  204. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_camera.py +0 -0
  205. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_categories.py +0 -0
  206. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_code.py +0 -0
  207. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  208. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  209. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  210. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  211. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  212. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  213. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_dev.py +0 -0
  214. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_energy.py +0 -0
  215. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_entities.py +0 -0
  216. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  217. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_groups.py +0 -0
  218. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_hacs.py +0 -0
  219. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_history.py +0 -0
  220. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_integrations.py +0 -0
  221. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_labels.py +0 -0
  222. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_radio.py +0 -0
  223. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_registry.py +0 -0
  224. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_resources.py +0 -0
  225. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_search.py +0 -0
  226. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  227. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_service.py +0 -0
  228. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_services.py +0 -0
  229. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_system.py +0 -0
  230. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_themes.py +0 -0
  231. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_todo.py +0 -0
  232. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_traces.py +0 -0
  233. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_updates.py +0 -0
  234. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  235. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  236. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  237. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/tools_zones.py +0 -0
  238. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/util_helpers.py +0 -0
  239. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/tools/validation_middleware.py +0 -0
  240. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/transforms/__init__.py +0 -0
  241. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/transforms/categorized_search.py +0 -0
  242. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  243. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/transport_security.py +0 -0
  244. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/update_check.py +0 -0
  245. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/__init__.py +0 -0
  246. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/config_hash.py +0 -0
  247. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/config_write_lock.py +0 -0
  248. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/data_paths.py +0 -0
  249. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/domain_handlers.py +0 -0
  250. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  251. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  252. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/operation_manager.py +0 -0
  253. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/python_sandbox.py +0 -0
  254. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/skill_loader.py +0 -0
  255. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/utils/usage_logger.py +0 -0
  256. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/visibility/__init__.py +0 -0
  257. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/visibility/enforcement.py +0 -0
  258. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/visibility/model.py +0 -0
  259. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/visibility/persistence.py +0 -0
  260. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp/visibility/resolver.py +0 -0
  261. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  262. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  263. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  264. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  265. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/tests/__init__.py +0 -0
  266. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/tests/test_constants.py +0 -0
  267. {ha_mcp_dev-8.2.0.dev2321 → ha_mcp_dev-8.2.0.dev2323}/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.2.0.dev2321
3
+ Version: 8.2.0.dev2323
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.2.0.dev2321"
7
+ version = "8.2.0.dev2323"
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
@@ -40,6 +40,10 @@ logger = logging.getLogger(__name__)
40
40
  # so the request did not execute and retrying is safe even for writes.
41
41
  _RETRYABLE_STATUS = frozenset({502, 503, 504})
42
42
  _MAX_REQUEST_ATTEMPTS = 3
43
+ # Journald window requested for the Core error log on Supervisor-backed
44
+ # installs. Both such branches of get_error_log() build their request from this
45
+ # constant, so the window they ask for cannot drift apart.
46
+ _ERROR_LOG_LINES = 20000
43
47
 
44
48
 
45
49
  class HomeAssistantError(Exception):
@@ -586,7 +590,11 @@ class HomeAssistantClient:
586
590
  """
587
591
  if is_running_in_addon():
588
592
  logger.debug("Fetching error log via Supervisor direct (core service)")
589
- return await self._supervisor_logs_get("core")
593
+ # An explicit `lines` is required: without it Supervisor applies its
594
+ # 100-line default, which is far too short a slice to tell what keeps
595
+ # repeating. `_get_supervisor_log` plumbs the same parameter for the
596
+ # same reason (#1734).
597
+ return await self._supervisor_logs_get("core", lines=_ERROR_LOG_LINES)
590
598
 
591
599
  if await self._is_supervised_install():
592
600
  logger.debug(
@@ -594,7 +602,7 @@ class HomeAssistantClient:
594
602
  )
595
603
  raw_response = await self._raw_request(
596
604
  "GET",
597
- "/hassio/core/logs?lines=20000",
605
+ f"/hassio/core/logs?lines={_ERROR_LOG_LINES}",
598
606
  headers={"Accept": "text/plain"},
599
607
  )
600
608
  return raw_response.text
@@ -619,11 +627,21 @@ class HomeAssistantClient:
619
627
  glitch or HTTP error returns False without setting the cache,
620
628
  so the next call re-probes. This is the only path that
621
629
  intentionally fails open — caller proceeds on the historical
622
- Container branch (which on HAOS will also fail, but with a
623
- clearer 404 than a probe-side exception would surface).
624
-
625
- Auth / connection / HTTP errors are caught explicitly; runtime
626
- bugs (TypeError, AttributeError) and BaseException derivatives
630
+ Container branch.
631
+
632
+ A 401 is deliberately excluded from that fail-open: it is a
633
+ definitive answer about the credential rather than a transient
634
+ glitch, and swallowing it strands exactly the install class this
635
+ probe exists for. Failing open sends a Supervised caller down
636
+ the Container branch to ``/api/error_log``, which HA Core never
637
+ registers under ``SUPERVISOR`` (see ``get_error_log``), so the
638
+ auth failure resurfaces as a 404 and the caller answers with
639
+ connection advice instead of "re-create the LLAT". Letting
640
+ ``HomeAssistantAuthError`` propagate reaches the auth handler
641
+ directly. Transport / HTTP fail-open is unchanged.
642
+
643
+ Connection / HTTP errors are caught explicitly; runtime bugs
644
+ (TypeError, AttributeError) and BaseException derivatives
627
645
  (KeyboardInterrupt, CancelledError) deliberately propagate so
628
646
  they're not silenced as "not supervised".
629
647
  """
@@ -632,16 +650,17 @@ class HomeAssistantClient:
632
650
  try:
633
651
  config = await self._request("GET", "/config")
634
652
  except (
635
- HomeAssistantAuthError,
636
653
  HomeAssistantAPIError,
637
654
  HomeAssistantConnectionError,
638
655
  httpx.HTTPError,
639
656
  TimeoutError,
640
657
  ) as exc:
641
- # Fail-open on transport / HTTP-layer failures only. Note:
642
- # a 401 here likely means the LLA is bad and the /error_log
643
- # fallback will also 401 — the user gets a clearer auth error
644
- # from that path than a swallowed probe error would surface.
658
+ # Fail-open on transport / HTTP-layer failures only.
659
+ # HomeAssistantAuthError is deliberately absent: a 401 is a
660
+ # definitive verdict on the credential, and failing open on
661
+ # it routes a Supervised caller to /api/error_log — a route
662
+ # that does not exist there — turning the auth error into a
663
+ # 404 (see the docstring).
645
664
  # Logged at WARNING so it's visible at default log levels
646
665
  # without spamming on every call (probe runs at most once
647
666
  # per session per outcome).
@@ -0,0 +1,365 @@
1
+ """Structured summarisation of Home Assistant's raw error log.
2
+
3
+ ``ha_get_logs(source='error_log')`` returns raw text by default. A busy
4
+ instance produces a 50-200 KB log in which the same handful of errors repeat
5
+ thousands of times, so the raw form can exhaust an agent's context while
6
+ conveying very little. This module collapses it into counted, deduplicated,
7
+ component-grouped issues.
8
+
9
+ Split out of ``tools_utility`` under AGENTS.md § Module Size: the parsing is
10
+ self-contained and shares nothing with the tool plumbing but its input string.
11
+ """
12
+
13
+ import re
14
+ from typing import Any, NamedTuple
15
+
16
+ # Full HA log line, e.g.
17
+ # "2026-05-27 10:15:23.456 ERROR (MainThread) [homeassistant.components.zha] msg"
18
+ # Distinct from `_LOG_LEVEL_RE` in `tools_utility`, which only sniffs the level
19
+ # out of a line for the raw path's filter.
20
+ # The thread field is matched lazily instead of as `\([^)]*\)`: since Python 3.10
21
+ # an unnamed thread is called "Thread-1 (target_fn)", so the field itself can
22
+ # contain parentheses, and a class that cannot cross them drops every line those
23
+ # threads log.
24
+ _HA_LOG_LINE_RE = re.compile(
25
+ r"^(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?)"
26
+ r"\s+(DEBUG|INFO|WARNING|ERROR|CRITICAL)"
27
+ r"\s+\(.*?\)"
28
+ r"\s+\[([^\]]+)\]"
29
+ r"\s+(.+)$",
30
+ re.IGNORECASE,
31
+ )
32
+ _MAX_MESSAGE_LEN = 200
33
+ _COMPONENT_PREFIX_DEPTH = 3
34
+ _CUSTOM_COMPONENT_PREFIX_DEPTH = 2
35
+ _DEFAULT_TOP_N = 20
36
+ _MAX_COMPONENTS = 50
37
+ # Marks a message the 200-char cap cut short, so a truncated entry is never
38
+ # mistaken for the whole message.
39
+ _TRUNCATION_MARK = "…[truncated]"
40
+ # Tie-breaker for issues with equal counts: a same-count CRITICAL outranks an
41
+ # INFO. Ordering only — not a filter, and not tied to VALID_LOG_LEVELS.
42
+ _LEVEL_SEVERITY = {"CRITICAL": 4, "ERROR": 3, "WARNING": 2, "INFO": 1, "DEBUG": 0}
43
+ # Shared by both response paths, because an empty fetch reads as an all-clear on
44
+ # either one: an agent that receives no log content and no warning reports "no
45
+ # errors" to the user. Only the remedy differs, so each path appends its own.
46
+ _EMPTY_FETCH_WARNING = (
47
+ "A running Home Assistant always logs something, so this is an empty or "
48
+ "failed fetch, NOT evidence that the log is clean."
49
+ )
50
+
51
+ # Supervisor-backed installs (add-on, HAOS, supervised) read HA Core's journald
52
+ # stream, where every line is wrapped in ANSI colour codes:
53
+ # "\x1b[31m2026-08-02 08:12:27.290 ERROR (MainThread) [x] msg\x1b[0m"
54
+ # Only container/pip installs read the plain home-assistant.log file.
55
+ # Two independent reasons to strip, not one chain:
56
+ # * the leading code alone is already fatal — _HA_LOG_LINE_RE is ^-anchored
57
+ # and str.strip() does not remove ESC, so the line matches nothing and no
58
+ # `message` is produced at all;
59
+ # * the trailing reset is what a leading-only strip would leave behind: it
60
+ # lands inside `message` and splits the dedup key for one recurring error.
61
+ # Strip both ends, always.
62
+ _ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
63
+
64
+
65
+ def _strip_ansi(text: str) -> str:
66
+ """Remove ANSI escape sequences (journald colour codes) from a log line."""
67
+ return _ANSI_RE.sub("", text)
68
+
69
+
70
+ def _normalize_timestamp(timestamp: str) -> str:
71
+ """Put a matched timestamp on one date/time separator.
72
+
73
+ Every timestamp comparison downstream is lexicographic — the covered
74
+ window, an issue's ``first_seen``/``last_seen`` bounds, and the recency
75
+ tiebreaker. ``_HA_LOG_LINE_RE`` accepts both the space and the ``T`` form
76
+ (case-insensitively), and ``" "`` sorts below every digit while ``"T"``
77
+ sorts above them, so a stream mixing the two forms would corrupt all three
78
+ at once. HA core emits the space form, so normalize onto that: real logs
79
+ come out byte-identical and only the ISO-8601 variant is rewritten.
80
+
81
+ The date is fixed-width in the pattern (``\\d{4}-\\d{2}-\\d{2}``), so the
82
+ separator is always at index 10 — replacing by position also covers the
83
+ lowercase ``t`` that ``re.IGNORECASE`` admits.
84
+ """
85
+ return timestamp[:10] + " " + timestamp[11:]
86
+
87
+
88
+ def _get_component_prefix(logger_name: str) -> str:
89
+ """Extract the component prefix (first N dotted segments) of a logger name.
90
+
91
+ A custom integration's identity is ``custom_components.<domain>`` — one
92
+ segment shallower than a core component — so its modules roll up into one
93
+ bucket instead of one per module.
94
+ """
95
+ parts = logger_name.split(".")
96
+ depth = (
97
+ _CUSTOM_COMPONENT_PREFIX_DEPTH
98
+ if parts[0] == "custom_components"
99
+ else _COMPONENT_PREFIX_DEPTH
100
+ )
101
+ if len(parts) >= depth:
102
+ return ".".join(parts[:depth])
103
+ return logger_name
104
+
105
+
106
+ def _truncate_message(message: str) -> str:
107
+ """Cap a message for display, marking it when it was cut."""
108
+ if len(message) <= _MAX_MESSAGE_LEN:
109
+ return message
110
+ return message[:_MAX_MESSAGE_LEN] + _TRUNCATION_MARK
111
+
112
+
113
+ class _ExtractedLines(NamedTuple):
114
+ """Tallies from one pass over the raw log; every input line lands in one.
115
+
116
+ ``matched`` counts lines that are valid HA log lines *before* ``level`` and
117
+ ``search`` run; ``entries`` holds the subset that survived them. The two are
118
+ kept apart because they answer different questions: only ``matched == 0``
119
+ means the parser could not read the log, while ``matched > 0`` with no
120
+ entries just means the filters excluded everything.
121
+
122
+ ``total input lines == blank + unparseable + matched`` holds by construction.
123
+ """
124
+
125
+ entries: list[dict[str, str]]
126
+ matched: int
127
+ unparseable: int
128
+ blank: int
129
+ window_start: str | None
130
+ window_end: str | None
131
+
132
+
133
+ def _extract_log_entries(
134
+ lines: list[str],
135
+ search: str | None = None,
136
+ level: str | None = None,
137
+ ) -> _ExtractedLines:
138
+ """Parse raw log lines into entries plus the per-line tallies around them.
139
+
140
+ Split out from ``_parse_error_log_structured`` to keep both under the repo's
141
+ complexity ceiling. A line counts as unparseable only when it does not match
142
+ the HA log format at all — lines dropped by ``level``/``search`` are simply
143
+ absent from ``entries``, never counted as unparseable.
144
+ """
145
+ entries: list[dict[str, str]] = []
146
+ matched = 0
147
+ unparseable = 0
148
+ blank = 0
149
+ window_start: str | None = None
150
+ window_end: str | None = None
151
+ needle = search.lower() if search else None
152
+
153
+ for line in lines:
154
+ # Strip ANSI *before* matching: on Supervisor-backed installs the line
155
+ # arrives colour-wrapped. Both ends matter for separate reasons — the
156
+ # leading code alone already defeats the ^-anchor so the line matches
157
+ # nothing, and a leading-only strip would leave the trailing reset
158
+ # inside `message`, splitting the dedup key for one recurring error.
159
+ clean = _strip_ansi(line).strip()
160
+ if not clean:
161
+ blank += 1
162
+ continue
163
+ match = _HA_LOG_LINE_RE.match(clean)
164
+ if not match:
165
+ unparseable += 1
166
+ continue
167
+ matched += 1
168
+ timestamp, log_level, logger_name, message = match.groups()
169
+ timestamp = _normalize_timestamp(timestamp)
170
+ # Widen the covered window on every parseable line, filtered or not:
171
+ # it describes the log slice that was read, not the summary's contents.
172
+ if window_start is None or timestamp < window_start:
173
+ window_start = timestamp
174
+ if window_end is None or timestamp > window_end:
175
+ window_end = timestamp
176
+ log_level = log_level.upper()
177
+ if level and log_level != level:
178
+ continue
179
+ if (
180
+ needle
181
+ and needle not in message.lower()
182
+ and needle not in logger_name.lower()
183
+ ):
184
+ continue
185
+ entries.append(
186
+ {
187
+ "timestamp": timestamp,
188
+ "level": log_level,
189
+ "logger": logger_name,
190
+ # Kept whole: the display cap is applied at dedup time, because
191
+ # keying on a capped message merges two errors that differ only
192
+ # past the cap into one issue with a summed count.
193
+ "message": message,
194
+ }
195
+ )
196
+
197
+ return _ExtractedLines(
198
+ entries=entries,
199
+ matched=matched,
200
+ unparseable=unparseable,
201
+ blank=blank,
202
+ window_start=window_start,
203
+ window_end=window_end,
204
+ )
205
+
206
+
207
+ def _parse_error_log_structured(
208
+ raw_text: str,
209
+ search: str | None = None,
210
+ level: str | None = None,
211
+ top_n: int = _DEFAULT_TOP_N,
212
+ ) -> dict[str, Any]:
213
+ """Parse the raw error log into a deduplicated, component-grouped summary.
214
+
215
+ ``source='error_log'`` normally returns raw text. A busy instance produces a
216
+ 50-200 KB log in which the same handful of errors repeat thousands of times,
217
+ so the raw form can exhaust an agent's context while conveying very little.
218
+ This collapses identical (level, logger, message) triples into counted
219
+ issues, groups them by component, and returns only the ``top_n`` noisiest.
220
+
221
+ Every input line is accounted for exactly once, because "not in the summary"
222
+ has several causes that must not be conflated:
223
+
224
+ * ``total_raw_lines`` - lines in the input, and the sum of the next three
225
+ * ``blank_lines`` - empty after ANSI stripping
226
+ * ``unparseable_lines`` - not the HA log format at all (traceback bodies,
227
+ continuation lines, format drift)
228
+ * ``matched_lines`` - valid log lines, before ``level``/``search``
229
+ * ``parsed_entries`` - the subset of ``matched_lines`` the filters kept
230
+
231
+ Only ``matched_lines == 0`` says the parser could not read the log; a small
232
+ ``parsed_entries`` under a narrow filter is the feature working, not data
233
+ loss. Counts are bounded by the fetched window (``window_start`` ..
234
+ ``window_end``): Supervisor-backed installs read a capped journald slice, so
235
+ there the window can be far shorter than the log's full history.
236
+ """
237
+ lines = raw_text.splitlines() if raw_text else []
238
+ total_raw_lines = len(lines)
239
+ extracted = _extract_log_entries(lines, search=search, level=level)
240
+ parsed = extracted.entries
241
+
242
+ # Dedupe on (level, logger, message), keyed on the FULL message: two errors
243
+ # that differ only past the display cap are distinct issues, and a capped
244
+ # key merges them into one with a summed count. Level belongs in the key
245
+ # too: the same text is logged at different levels, and a level-less key
246
+ # would report a later ERROR under the level of the first line to carry it.
247
+ dedup: dict[tuple[str, str, str], dict[str, Any]] = {}
248
+ for entry in parsed:
249
+ key = (entry["level"], entry["logger"], entry["message"])
250
+ record = dedup.get(key)
251
+ if record is None:
252
+ record = {
253
+ "logger": entry["logger"],
254
+ "level": entry["level"],
255
+ "message": _truncate_message(entry["message"]),
256
+ "count": 0,
257
+ "first_seen": entry["timestamp"],
258
+ "last_seen": entry["timestamp"],
259
+ }
260
+ dedup[key] = record
261
+ record["count"] += 1
262
+ # Bounds, not last-write-wins: a log whose lines are not perfectly
263
+ # ordered would otherwise drag last_seen backwards, which also
264
+ # misplaces the issue in the recency tiebreaker below.
265
+ record["first_seen"] = min(record["first_seen"], entry["timestamp"])
266
+ record["last_seen"] = max(record["last_seen"], entry["timestamp"])
267
+
268
+ # Count first, then severity, then recency. Python's sort is stable, so
269
+ # count alone would return the *oldest* of a tied run — and on a log whose
270
+ # messages embed volatile ids every issue ties at count 1, which quietly
271
+ # turns "top 20 issues" into "the 20 oldest".
272
+ all_issues = sorted(
273
+ dedup.values(),
274
+ key=lambda i: (
275
+ i["count"],
276
+ _LEVEL_SEVERITY.get(i["level"], 0),
277
+ i["last_seen"],
278
+ ),
279
+ reverse=True,
280
+ )
281
+
282
+ by_component: dict[str, dict[str, Any]] = {}
283
+ for issue in all_issues:
284
+ prefix = _get_component_prefix(issue["logger"])
285
+ bucket = by_component.setdefault(
286
+ prefix, {"total_occurrences": 0, "issue_count": 0}
287
+ )
288
+ bucket["total_occurrences"] += issue["count"]
289
+ bucket["issue_count"] += 1
290
+
291
+ top_issues = [
292
+ {
293
+ "component": _get_component_prefix(issue["logger"]),
294
+ "logger": issue["logger"],
295
+ "level": issue["level"],
296
+ "message": issue["message"],
297
+ "count": issue["count"],
298
+ "first_seen": issue["first_seen"],
299
+ "last_seen": issue["last_seen"],
300
+ }
301
+ for issue in all_issues[:top_n]
302
+ ]
303
+
304
+ # Cap by_component too: a log with thousands of distinct loggers would
305
+ # otherwise make the response unbounded, contradicting the whole premise.
306
+ ranked_components = sorted(
307
+ by_component.items(),
308
+ key=lambda kv: kv[1]["total_occurrences"],
309
+ reverse=True,
310
+ )
311
+ component_table = dict(ranked_components[:_MAX_COMPONENTS])
312
+
313
+ summary: dict[str, Any] = {
314
+ "total_raw_lines": total_raw_lines,
315
+ "blank_lines": extracted.blank,
316
+ "unparseable_lines": extracted.unparseable,
317
+ "matched_lines": extracted.matched,
318
+ "parsed_entries": len(parsed),
319
+ "unique_issues": len(all_issues),
320
+ "components_affected": len(by_component),
321
+ "showing_top_n": min(top_n, len(all_issues)),
322
+ "showing_components": len(component_table),
323
+ # Counts describe this window only. On Supervisor-backed installs the
324
+ # fetch is a capped journald slice, so an issue's count here is not its
325
+ # count since it first occurred.
326
+ "window_start": extracted.window_start,
327
+ "window_end": extracted.window_end,
328
+ }
329
+
330
+ result: dict[str, Any] = {
331
+ "success": True,
332
+ "source": "error_log",
333
+ "structured": True,
334
+ "summary": summary,
335
+ "top_issues": top_issues,
336
+ "by_component": component_table,
337
+ }
338
+
339
+ # An empty summary has three causes and they need different answers, so the
340
+ # branch keys on `matched_lines` — the pre-filter count. Keying on the raw
341
+ # unparseable count instead reports format drift for an ordinary filtered
342
+ # result, because tracebacks alone push a healthy log past 90% unparseable.
343
+ # Degraded-operation notices go in the top-level `warnings` list — the one
344
+ # channel this repo's tools use — never a singular `warning` string.
345
+ if extracted.matched == 0:
346
+ if extracted.unparseable > 0:
347
+ result["warnings"] = [
348
+ f"No log lines could be parsed ({extracted.unparseable} of "
349
+ f"{total_raw_lines} did not match the expected Home Assistant log "
350
+ "format). This summary is NOT evidence that the log is clean - "
351
+ "re-run with structured=False to read the raw text."
352
+ ]
353
+ else:
354
+ result["warnings"] = [
355
+ f"No log entries arrived to parse ({total_raw_lines} lines "
356
+ f"fetched, none of them log entries). {_EMPTY_FETCH_WARNING} "
357
+ "Re-run with structured=False to see the raw response."
358
+ ]
359
+ elif not parsed:
360
+ result["warnings"] = [
361
+ f"None of the {extracted.matched} parsed log entries matched the "
362
+ "requested filters. The log itself parsed fine, so this reflects "
363
+ "the filters, not a parse failure."
364
+ ]
365
+ return result