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.
- {ha_mcp_dev-8.4.3.dev2542/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.4.3.dev2546}/PKG-INFO +1 -1
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/pyproject.toml +1 -1
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/backup_manager.py +54 -6
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/config.py +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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/settings.js +1 -1
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/log_common.py +4 -4
- ha_mcp_dev-8.4.3.dev2546/src/ha_mcp/tools/log_sources_fault.py +295 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_filesystem.py +4 -2
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_logs.py +29 -9
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/LICENSE +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/README.md +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/setup.cfg +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_rich_compat.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/requirements.txt +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/client.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/server.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/version.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/auth/oidc_compat.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/hacs_auto_refresh.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/log_filters.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/editing.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/redaction.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/renamed_tools.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/blueprint_sources.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/blueprint_substitute.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/blueprint_write.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/bulk_selector.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_config_entries.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {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
- {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
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_identity.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/error_log_parsing.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/hacs_registration.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/log_sources.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/log_sources_supervisor.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_security_policy.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/device_registry_semantics.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/entity_membership.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/visibility/resolver.py +0 -0
- {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
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {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
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/tests/test_env_manager.py +0 -0
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
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`` —
|
|
780
|
-
#
|
|
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`` —
|
|
1660
|
-
#
|
|
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).
|
{ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/settings_ui/locales/en.json
RENAMED
|
@@ -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
|
|
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: '
|
|
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'
|
|
146
|
-
f"'
|
|
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
|
|
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):
|
|
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'
|
|
201
|
-
"(ignored for other sources). On error_log it
|
|
202
|
-
"log lines back from the newest entry;
|
|
203
|
-
"
|
|
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.
|
|
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`
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256
RENAMED
|
File without changes
|
{ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/VENDORED
RENAMED
|
File without changes
|
{ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-8.4.3.dev2542 → ha_mcp_dev-8.4.3.dev2546}/src/ha_mcp/_vendor/websockets/__main__.py
RENAMED
|
File without changes
|