ha-mcp-dev 8.3.0.dev2442__tar.gz → 8.3.0.dev2445__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.3.0.dev2442/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2445}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/pyproject.toml +1 -1
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/client/rest_client.py +262 -50
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_handlers_server.py +30 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/en.json +2 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/settings.js +32 -8
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/error_log_parsing.py +208 -4
- ha_mcp_dev-8.3.0.dev2445/src/ha_mcp/tools/log_common.py +199 -0
- ha_mcp_dev-8.3.0.dev2445/src/ha_mcp/tools/log_sources.py +764 -0
- ha_mcp_dev-8.3.0.dev2445/src/ha_mcp/tools/log_sources_supervisor.py +326 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/registry.py +12 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_bug_report.py +15 -6
- ha_mcp_dev-8.3.0.dev2445/src/ha_mcp/tools/tools_logs.py +296 -0
- ha_mcp_dev-8.3.0.dev2445/src/ha_mcp/tools/tools_utility.py +277 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp_dev.egg-info/SOURCES.txt +4 -0
- ha_mcp_dev-8.3.0.dev2442/src/ha_mcp/tools/tools_utility.py +0 -1600
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/setup.cfg +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_rich_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/requirements.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_vendor/websockets/version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/auth/oidc_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/hacs_auto_refresh.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/log_filters.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/editing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/redaction.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/renamed_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/bulk_selector.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/component_config_entries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/config_entry_identity.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/hacs_registration.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_security_policy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/entity_membership.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/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.3.0.
|
|
7
|
+
version = "8.3.0.dev2445"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# Upper bound widened to <3.15 so the package installs on Home Assistant's
|
|
@@ -8,6 +8,7 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
import ssl
|
|
10
10
|
import time
|
|
11
|
+
from collections.abc import Awaitable, Callable
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from typing import Any, NoReturn
|
|
13
14
|
|
|
@@ -52,10 +53,23 @@ logger = logging.getLogger(__name__)
|
|
|
52
53
|
_RETRYABLE_GATEWAY_STATUS = frozenset({502, 503, 504})
|
|
53
54
|
_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
|
|
54
55
|
_MAX_REQUEST_ATTEMPTS = 3
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
|
|
56
|
+
# Smallest log window this client requests. Supervisor's Range syntax addresses
|
|
57
|
+
# a newest-anchored window by its first entry, so a one-entry one is
|
|
58
|
+
# inexpressible (``entries=:0:1`` reads from the OLDEST entry instead);
|
|
59
|
+
# Supervisor coerces its own ``?lines=1`` to 2 (supervisor/api/host.py) and this
|
|
60
|
+
# client mirrors that on every route, so all install types serve the same slice.
|
|
61
|
+
# Shared with the tool layer, which sizes its fetch window against it.
|
|
62
|
+
MIN_LOG_WINDOW_LINES = 2
|
|
63
|
+
# Entries the has_more probe reads from just behind a full window. A one-entry
|
|
64
|
+
# probe compared one rendered line, which a boundary duplicate (the same
|
|
65
|
+
# timestamp-less traceback line logged twice) could false-match into "end of
|
|
66
|
+
# history". Comparing a block this deep requires that many CONSECUTIVE
|
|
67
|
+
# identical lines to straddle the boundary — and then the history a false stop
|
|
68
|
+
# skips is more of the same duplicates. Timestamps cannot close the gap
|
|
69
|
+
# instead: the colliding lines are exactly the ones without them, and
|
|
70
|
+
# journald's cursor/timestamp metadata exists only in the journal export
|
|
71
|
+
# format, which the hassio-proxy route cannot request.
|
|
72
|
+
_PROBE_ENTRIES = 8
|
|
59
73
|
|
|
60
74
|
|
|
61
75
|
class HomeAssistantError(Exception):
|
|
@@ -96,7 +110,7 @@ class HomeAssistantAuthError(HomeAssistantError):
|
|
|
96
110
|
auth errors as part of the local "this entity is not registered yet"
|
|
97
111
|
polling logic. Sites that specifically need to catch both must list
|
|
98
112
|
them explicitly (see ``_get_supervisor_log`` and
|
|
99
|
-
``_get_system_service_log`` in ``
|
|
113
|
+
``_get_system_service_log`` in ``log_sources_supervisor.py``).
|
|
100
114
|
"""
|
|
101
115
|
|
|
102
116
|
|
|
@@ -114,6 +128,19 @@ class HomeAssistantAPIError(HomeAssistantError):
|
|
|
114
128
|
self.response_data = response_data
|
|
115
129
|
|
|
116
130
|
|
|
131
|
+
@dataclass(frozen=True)
|
|
132
|
+
class ErrorLogPage:
|
|
133
|
+
"""One bounded window of the error log, plus whether older history exists.
|
|
134
|
+
|
|
135
|
+
``has_more`` cannot be derived by the caller from ``text``: a journald
|
|
136
|
+
window that comes back full may or may not have anything behind it (see
|
|
137
|
+
``_journald_error_log_page``), and only the fetching route knows which.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
text: str
|
|
141
|
+
has_more: bool
|
|
142
|
+
|
|
143
|
+
|
|
117
144
|
@dataclass(frozen=True)
|
|
118
145
|
class SceneResolution:
|
|
119
146
|
"""Outcome of resolving a scene identifier via the entity registry.
|
|
@@ -579,8 +606,140 @@ class HomeAssistantClient:
|
|
|
579
606
|
logger.debug("Checking configuration")
|
|
580
607
|
return await self._request("POST", "/config/core/check_config")
|
|
581
608
|
|
|
582
|
-
|
|
583
|
-
|
|
609
|
+
@staticmethod
|
|
610
|
+
def _journald_range_header(lines: int, offset: int = 0) -> str:
|
|
611
|
+
"""Build Supervisor's journald ``Range`` header for a bounded window.
|
|
612
|
+
|
|
613
|
+
Supervisor's log endpoints accept
|
|
614
|
+
``Range: entries=<cursor>:<num_skip>:<num_entries>`` and build
|
|
615
|
+
``entries=:-{N-1}:{N}`` themselves for a ``?lines=N`` request
|
|
616
|
+
(supervisor/api/host.py). ``offset`` moves the window that many entries
|
|
617
|
+
further back, so the returned slice ends ``offset`` entries before the
|
|
618
|
+
newest one.
|
|
619
|
+
|
|
620
|
+
A ``lines=1`` window is only expressible with a non-zero ``offset``: at
|
|
621
|
+
``offset=0`` the header degenerates to ``entries=:0:1``, whose
|
|
622
|
+
non-negative skip reads from the OLDEST entry instead of the newest.
|
|
623
|
+
Callers anchoring at the newest entry pass ``lines`` already floored at
|
|
624
|
+
``MIN_LOG_WINDOW_LINES``; the one-entry probe always carries an offset.
|
|
625
|
+
"""
|
|
626
|
+
# Negate the computed skip rather than prefixing "-" so the degenerate
|
|
627
|
+
# skip of 0 renders as ":0:" (non-negative, reads from the oldest
|
|
628
|
+
# entry) instead of the ill-formed ":-0:".
|
|
629
|
+
return f"entries=:{-(offset + lines - 1)}:{lines}"
|
|
630
|
+
|
|
631
|
+
@staticmethod
|
|
632
|
+
def _container_error_log_page(text: str, lines: int, offset: int) -> ErrorLogPage:
|
|
633
|
+
"""Window the whole-file ``/api/error_log`` body client-side.
|
|
634
|
+
|
|
635
|
+
Same tail semantics as the journald Range the Supervisor-backed
|
|
636
|
+
branches request, so ``get_error_log`` answers with the same slice of
|
|
637
|
+
history on every install type. Paging is exact here — the whole file is
|
|
638
|
+
in hand, so ``has_more`` is simply whether anything precedes the window.
|
|
639
|
+
An ``offset`` past the start of the file yields an empty final page.
|
|
640
|
+
"""
|
|
641
|
+
all_lines = text.splitlines()
|
|
642
|
+
end = len(all_lines) - offset
|
|
643
|
+
if end <= 0:
|
|
644
|
+
return ErrorLogPage(text="", has_more=False)
|
|
645
|
+
start = max(end - lines, 0)
|
|
646
|
+
return ErrorLogPage(text="\n".join(all_lines[start:end]), has_more=start > 0)
|
|
647
|
+
|
|
648
|
+
async def _supervisor_core_logs(self, lines: int, offset: int) -> str:
|
|
649
|
+
"""Fetch a journald window of HA Core's log straight from Supervisor."""
|
|
650
|
+
return await self._supervisor_logs_get("core", lines=lines, offset=offset)
|
|
651
|
+
|
|
652
|
+
async def _proxied_core_logs(self, lines: int, offset: int) -> str:
|
|
653
|
+
"""Fetch the same window through HA Core's hassio proxy with the LLA."""
|
|
654
|
+
response = await self._raw_request(
|
|
655
|
+
"GET",
|
|
656
|
+
"/hassio/core/logs",
|
|
657
|
+
headers={
|
|
658
|
+
"Accept": "text/plain",
|
|
659
|
+
"Range": self._journald_range_header(lines, offset),
|
|
660
|
+
},
|
|
661
|
+
)
|
|
662
|
+
return response.text
|
|
663
|
+
|
|
664
|
+
async def _journald_error_log_page(
|
|
665
|
+
self,
|
|
666
|
+
lines: int,
|
|
667
|
+
offset: int,
|
|
668
|
+
fetch: Callable[[int, int], Awaitable[str]],
|
|
669
|
+
) -> ErrorLogPage:
|
|
670
|
+
"""Read one journald window and settle ``has_more`` with a 1-entry probe.
|
|
671
|
+
|
|
672
|
+
systemd's journal-gatewayd does not guard the negative-skip branch the
|
|
673
|
+
way it guards the positive one (``sd_journal_previous_skip(-n_skip + 1)``
|
|
674
|
+
returns no END_OF_STREAM): an offset past the start of the journal
|
|
675
|
+
CLAMPS to the oldest entry and still answers with a full window. So a
|
|
676
|
+
saturated window proves nothing about what lies behind it, and a
|
|
677
|
+
count-based ``has_more`` would stay true forever — an agent following
|
|
678
|
+
the pagination hint would loop on identical pages.
|
|
679
|
+
|
|
680
|
+
Hence: a short or empty window means the journal really is exhausted
|
|
681
|
+
(nothing clamps a window smaller), and a saturated one is settled by
|
|
682
|
+
asking for the ``_PROBE_ENTRIES`` entries that should sit just behind
|
|
683
|
+
it. If the journal has nothing there, the clamp starts the probe at
|
|
684
|
+
the same oldest entry the window starts with, so the probe block
|
|
685
|
+
equals the window's opening lines — identity means end of history. A
|
|
686
|
+
differing block proves deeper history (an under-full span behind the
|
|
687
|
+
window also clamps and misaligns, which correctly reads as "more").
|
|
688
|
+
The identity check also terminates the degraded case where an
|
|
689
|
+
intermediary strips the ``Range`` header (RFC 7233 permits it): both
|
|
690
|
+
requests then return Supervisor's default window and compare equal.
|
|
691
|
+
|
|
692
|
+
The saturation test counts lines only to decide whether to probe, never
|
|
693
|
+
to answer: one journald entry can be a multi-line traceback, so the line
|
|
694
|
+
count over-estimates entries and can only over-trigger the probe, which
|
|
695
|
+
then gives the real answer. In practice the two rarely diverge here:
|
|
696
|
+
docker's journald log driver — how HAOS captures Core's output —
|
|
697
|
+
ingests stdout line by line, one journal entry per rendered line, so
|
|
698
|
+
multi-line entries reach this endpoint only from native journal-API
|
|
699
|
+
writers.
|
|
700
|
+
|
|
701
|
+
Known imprecision, accepted: the identity check can still stop one
|
|
702
|
+
page early when the compared block — ``_PROBE_ENTRIES`` lines, or the
|
|
703
|
+
window's own length when that is smaller — is byte-identical across
|
|
704
|
+
the boundary (see the constant's comment for why timestamps cannot
|
|
705
|
+
disambiguate instead). The failure
|
|
706
|
+
direction is a missed page of exactly those duplicates — bounded,
|
|
707
|
+
unlike the infinite loop a count-based ``has_more`` produces on the
|
|
708
|
+
clamp.
|
|
709
|
+
"""
|
|
710
|
+
text = await fetch(lines, offset)
|
|
711
|
+
if not text or len(text.splitlines()) < lines:
|
|
712
|
+
return ErrorLogPage(text=text, has_more=False)
|
|
713
|
+
probe = await fetch(_PROBE_ENTRIES, offset + lines)
|
|
714
|
+
probe_lines = probe.splitlines()
|
|
715
|
+
window_lines = text.splitlines()
|
|
716
|
+
# Compare the shared-length prefix: with a window smaller than the
|
|
717
|
+
# probe (lines < _PROBE_ENTRIES), an end-of-history clamp returns a
|
|
718
|
+
# probe LONGER than the window, and a full-length compare would read
|
|
719
|
+
# that as "more history" forever — the small-limit loop this method
|
|
720
|
+
# exists to prevent.
|
|
721
|
+
shared = min(len(probe_lines), len(window_lines))
|
|
722
|
+
return ErrorLogPage(
|
|
723
|
+
text=text,
|
|
724
|
+
has_more=shared > 0 and probe_lines[:shared] != window_lines[:shared],
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
async def get_error_log(self, lines: int, offset: int = 0) -> ErrorLogPage:
|
|
728
|
+
"""Get a bounded window of the Home Assistant error log.
|
|
729
|
+
|
|
730
|
+
``lines`` is the number of log lines to fetch and ``offset`` how many
|
|
731
|
+
newer lines to skip, counted back from the most recent one; both are
|
|
732
|
+
floored/served at ``MIN_LOG_WINDOW_LINES``. Returns the window text and
|
|
733
|
+
whether older history remains behind it. The window is mandatory: the
|
|
734
|
+
unconditional 20,000-line fetch this replaces made Supervisor assemble
|
|
735
|
+
a journald slice for 15+ minutes on real hardware (#2279).
|
|
736
|
+
|
|
737
|
+
The whole body runs under one ``asyncio.timeout(self.timeout)``. That
|
|
738
|
+
is the only bound that holds here: ``httpx.Timeout`` applies per I/O
|
|
739
|
+
operation, so a server trickling bytes while it assembles the body
|
|
740
|
+
resets the read timeout indefinitely and no per-attempt timeout ever
|
|
741
|
+
fires — the mechanism behind #2279's hang. The deadline covers the
|
|
742
|
+
install-class probe, the log fetch and the ``has_more`` probe together.
|
|
584
743
|
|
|
585
744
|
Three-way branch depending on how this client reaches HA:
|
|
586
745
|
|
|
@@ -601,6 +760,17 @@ class HomeAssistantClient:
|
|
|
601
760
|
- **External client → Container/pip HA** (neither of the above):
|
|
602
761
|
keep the historical ``/api/error_log`` proxy path.
|
|
603
762
|
|
|
763
|
+
The two Supervisor-backed branches express the window as a journald
|
|
764
|
+
``Range`` header (see ``_journald_range_header``) and settle
|
|
765
|
+
``has_more`` with a one-entry probe (see ``_journald_error_log_page``).
|
|
766
|
+
HA Core's hassio proxy forwards that header for its log paths
|
|
767
|
+
(``PATHS_LOGS`` in ``homeassistant/components/hassio/http.py``); an
|
|
768
|
+
intermediary that stripped it would degrade to Supervisor's 100-line
|
|
769
|
+
default, never to an unbounded fetch, and the probe still terminates
|
|
770
|
+
paging. ``/api/error_log`` is a whole-file plain-text endpoint with no
|
|
771
|
+
window support, so the container branch reads the file and applies the
|
|
772
|
+
same window client-side, where paging is exact.
|
|
773
|
+
|
|
604
774
|
The middle branch was discovered by the HAOS E2E tier (#1326): the
|
|
605
775
|
test harness runs ha-mcp externally against a booted HAOS, hits
|
|
606
776
|
the unregistered endpoint, and the old binary branch surfaced as
|
|
@@ -615,30 +785,36 @@ class HomeAssistantClient:
|
|
|
615
785
|
HomeAssistantConnectionError: Network, timeout, or transport
|
|
616
786
|
error.
|
|
617
787
|
"""
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
"Fetching error log via HA Core /hassio/core/logs proxy (supervised)"
|
|
629
|
-
)
|
|
630
|
-
raw_response = await self._raw_request(
|
|
631
|
-
"GET",
|
|
632
|
-
f"/hassio/core/logs?lines={_ERROR_LOG_LINES}",
|
|
633
|
-
headers={"Accept": "text/plain"},
|
|
634
|
-
)
|
|
635
|
-
return raw_response.text
|
|
788
|
+
window = max(lines, MIN_LOG_WINDOW_LINES)
|
|
789
|
+
try:
|
|
790
|
+
async with asyncio.timeout(self.timeout):
|
|
791
|
+
if is_running_in_addon():
|
|
792
|
+
logger.debug(
|
|
793
|
+
"Fetching error log via Supervisor direct (core service)"
|
|
794
|
+
)
|
|
795
|
+
return await self._journald_error_log_page(
|
|
796
|
+
window, offset, self._supervisor_core_logs
|
|
797
|
+
)
|
|
636
798
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
799
|
+
if await self._is_supervised_install():
|
|
800
|
+
logger.debug(
|
|
801
|
+
"Fetching error log via HA Core /hassio/core/logs proxy "
|
|
802
|
+
"(supervised)"
|
|
803
|
+
)
|
|
804
|
+
return await self._journald_error_log_page(
|
|
805
|
+
window, offset, self._proxied_core_logs
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
logger.debug("Fetching error log via HA Core proxy (Container/pip)")
|
|
809
|
+
raw_response = await self._raw_request(
|
|
810
|
+
"GET", "/error_log", headers={"Accept": "text/plain"}
|
|
811
|
+
)
|
|
812
|
+
return self._container_error_log_page(raw_response.text, window, offset)
|
|
813
|
+
except TimeoutError as e:
|
|
814
|
+
raise HomeAssistantConnectionError(
|
|
815
|
+
f"Timeout fetching the error log after {self.timeout}s: "
|
|
816
|
+
f"{str(e) or type(e).__name__}"
|
|
817
|
+
) from e
|
|
642
818
|
|
|
643
819
|
async def _is_supervised_install(self) -> bool:
|
|
644
820
|
"""Detect whether the target HA is a Supervised / HAOS install.
|
|
@@ -742,12 +918,30 @@ class HomeAssistantClient:
|
|
|
742
918
|
return await self._get_addon_logs_via_supervisor(slug, lines=lines)
|
|
743
919
|
|
|
744
920
|
logger.debug(f"Fetching addon logs for slug={slug} via HA Core proxy")
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
921
|
+
return await self._proxied_logs_get(f"addons/{slug}", lines)
|
|
922
|
+
|
|
923
|
+
async def _proxied_logs_get(self, path: str, lines: int | None) -> str:
|
|
924
|
+
"""Fetch ``text/plain`` logs through HA Core's hassio proxy.
|
|
925
|
+
|
|
926
|
+
Wrapped in an overall wall-clock deadline for the same reason as the
|
|
927
|
+
error-log routes (#2279): ``httpx.Timeout`` applies per I/O operation,
|
|
928
|
+
so a server trickling bytes while assembling the body resets the read
|
|
929
|
+
timeout indefinitely, and the deadline is the only bound that holds.
|
|
930
|
+
It caps the whole ``_raw_request`` retry envelope.
|
|
931
|
+
"""
|
|
932
|
+
try:
|
|
933
|
+
async with asyncio.timeout(self.timeout):
|
|
934
|
+
response = await self._raw_request(
|
|
935
|
+
"GET",
|
|
936
|
+
f"/hassio/{path}/logs",
|
|
937
|
+
headers={"Accept": "text/plain"},
|
|
938
|
+
params={"lines": lines} if lines is not None else None,
|
|
939
|
+
)
|
|
940
|
+
except TimeoutError as e:
|
|
941
|
+
raise HomeAssistantConnectionError(
|
|
942
|
+
f"Timeout fetching /hassio/{path}/logs from Home Assistant "
|
|
943
|
+
f"after {self.timeout}s: {str(e) or type(e).__name__}"
|
|
944
|
+
) from e
|
|
751
945
|
return response.text
|
|
752
946
|
|
|
753
947
|
@staticmethod
|
|
@@ -772,7 +966,12 @@ class HomeAssistantClient:
|
|
|
772
966
|
message = text_body.strip() or reason_phrase or "<empty body>"
|
|
773
967
|
return message
|
|
774
968
|
|
|
775
|
-
async def _supervisor_logs_get(
|
|
969
|
+
async def _supervisor_logs_get(
|
|
970
|
+
self,
|
|
971
|
+
path: str,
|
|
972
|
+
lines: int | None = None,
|
|
973
|
+
offset: int | None = None,
|
|
974
|
+
) -> str:
|
|
776
975
|
"""Fetch ``text/plain`` logs from a Supervisor REST endpoint.
|
|
777
976
|
|
|
778
977
|
``path`` is everything between ``http://supervisor/`` and ``/logs``:
|
|
@@ -782,11 +981,22 @@ class HomeAssistantClient:
|
|
|
782
981
|
cli, multicast, observer}) for system-service logs
|
|
783
982
|
|
|
784
983
|
``lines`` maps to the endpoint's ``?lines=`` journald-window query
|
|
785
|
-
param; omitted → Supervisor's 100-line default window.
|
|
984
|
+
param; omitted → Supervisor's 100-line default window. Adding
|
|
985
|
+
``offset`` switches the same window to a ``Range: entries=`` header
|
|
986
|
+
(see ``_journald_range_header``), which is the only form that can
|
|
987
|
+
address an older window. The two forms are mutually exclusive on the
|
|
988
|
+
wire — Supervisor tests ``if "lines" in request.query`` first and
|
|
989
|
+
ignores a ``Range`` header whenever that query param is present — so
|
|
990
|
+
the offset form sends the window in the header and no query param,
|
|
991
|
+
rather than a pair that could contradict each other.
|
|
786
992
|
|
|
787
993
|
Bypasses ``HomeAssistantClient.httpx_client`` because that client targets
|
|
788
994
|
Home Assistant Core through ``http://supervisor/core/api``, while logs
|
|
789
|
-
belong to Supervisor at ``http://supervisor``.
|
|
995
|
+
belong to Supervisor at ``http://supervisor``. Plain HTTP here is the
|
|
996
|
+
platform contract, not a downgrade: Supervisor serves its API over
|
|
997
|
+
HTTP only (no TLS endpoint or IPC socket exists), on the internal
|
|
998
|
+
``hassio`` docker network whose isolation is the boundary protecting
|
|
999
|
+
the token — see SECURITY.md § "Local network is the trusted zone". Both clients use the same
|
|
790
1000
|
``SUPERVISOR_TOKEN``; the Core proxy requires ``homeassistant_api``, while
|
|
791
1001
|
system-service and arbitrary app-log paths require ``hassio_api`` and
|
|
792
1002
|
``hassio_role: manager``. The recognized-app-token exception is
|
|
@@ -815,11 +1025,19 @@ class HomeAssistantClient:
|
|
|
815
1025
|
)
|
|
816
1026
|
|
|
817
1027
|
relative_path = f"/{path}/logs"
|
|
1028
|
+
headers = {"Accept": "text/plain"}
|
|
1029
|
+
params = {"lines": lines} if lines is not None else None
|
|
1030
|
+
if offset is not None:
|
|
1031
|
+
# An offset window with no size asked for falls back to the
|
|
1032
|
+
# smallest one the Range syntax can express.
|
|
1033
|
+
size = lines if lines is not None else MIN_LOG_WINDOW_LINES
|
|
1034
|
+
headers["Range"] = self._journald_range_header(size, offset)
|
|
1035
|
+
params = None
|
|
818
1036
|
logger.debug(
|
|
819
|
-
"Fetching %s%s via Supervisor direct (
|
|
1037
|
+
"Fetching %s%s via Supervisor direct (%s)",
|
|
820
1038
|
get_supervisor_base_url(),
|
|
821
1039
|
relative_path,
|
|
822
|
-
lines,
|
|
1040
|
+
f"range={headers['Range']}" if offset is not None else f"lines={lines}",
|
|
823
1041
|
)
|
|
824
1042
|
|
|
825
1043
|
try:
|
|
@@ -830,8 +1048,8 @@ class HomeAssistantClient:
|
|
|
830
1048
|
) as client:
|
|
831
1049
|
response = await client.get(
|
|
832
1050
|
relative_path,
|
|
833
|
-
headers=
|
|
834
|
-
params=
|
|
1051
|
+
headers=headers,
|
|
1052
|
+
params=params,
|
|
835
1053
|
)
|
|
836
1054
|
except (TimeoutError, httpx.TimeoutException) as e:
|
|
837
1055
|
raise HomeAssistantConnectionError(
|
|
@@ -924,13 +1142,7 @@ class HomeAssistantClient:
|
|
|
924
1142
|
return await self._supervisor_logs_get(service, lines=lines)
|
|
925
1143
|
|
|
926
1144
|
logger.debug(f"Fetching {service} logs via HA Core proxy")
|
|
927
|
-
|
|
928
|
-
"GET",
|
|
929
|
-
f"/hassio/{service}/logs",
|
|
930
|
-
headers={"Accept": "text/plain"},
|
|
931
|
-
params={"lines": lines} if lines is not None else None,
|
|
932
|
-
)
|
|
933
|
-
return response.text
|
|
1145
|
+
return await self._proxied_logs_get(service, lines)
|
|
934
1146
|
|
|
935
1147
|
async def test_connection(self) -> tuple[bool, str | None]:
|
|
936
1148
|
"""
|
{ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/_handlers_server.py
RENAMED
|
@@ -19,6 +19,7 @@ import logging
|
|
|
19
19
|
import os
|
|
20
20
|
import time
|
|
21
21
|
import uuid
|
|
22
|
+
import weakref
|
|
22
23
|
from collections.abc import Callable, Container
|
|
23
24
|
from typing import TYPE_CHECKING, Any
|
|
24
25
|
|
|
@@ -323,6 +324,28 @@ async def _restart_addon(
|
|
|
323
324
|
# ---- Settings info ----
|
|
324
325
|
|
|
325
326
|
|
|
327
|
+
# Keyed by server instance so a replaced worker drops out with its instance;
|
|
328
|
+
# the module-level dict itself survives entry reloads (modules stay cached).
|
|
329
|
+
_WORKER_INSTANCE_IDS: weakref.WeakKeyDictionary[Any, str] = weakref.WeakKeyDictionary()
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _worker_instance_id(server: HomeAssistantSmartMCPServer) -> str:
|
|
333
|
+
"""Per-worker identity for the restart poll's embedded mode.
|
|
334
|
+
|
|
335
|
+
``_PROCESS_INSTANCE_ID`` proves a process swap, but an embedded restart
|
|
336
|
+
reloads the config entry INSIDE the surviving HA process — modules stay
|
|
337
|
+
cached, so the process id never flips and the poll timed out after every
|
|
338
|
+
successful reload (#2279 feedback). A new server instance is constructed
|
|
339
|
+
per entry setup, so an id lazily pinned to the instance flips exactly
|
|
340
|
+
when the reload completes.
|
|
341
|
+
"""
|
|
342
|
+
wid = _WORKER_INSTANCE_IDS.get(server)
|
|
343
|
+
if wid is None:
|
|
344
|
+
wid = uuid.uuid4().hex
|
|
345
|
+
_WORKER_INSTANCE_IDS[server] = wid
|
|
346
|
+
return wid
|
|
347
|
+
|
|
348
|
+
|
|
326
349
|
async def _settings_info(
|
|
327
350
|
server: HomeAssistantSmartMCPServer | None, is_sidecar: bool, _: Request
|
|
328
351
|
) -> JSONResponse:
|
|
@@ -355,6 +378,13 @@ async def _settings_info(
|
|
|
355
378
|
"sidecar" if is_sidecar else _detect_installation_method()
|
|
356
379
|
),
|
|
357
380
|
"instance_id": _PROCESS_INSTANCE_ID,
|
|
381
|
+
# Embedded restarts swap the server instance, not the process —
|
|
382
|
+
# the restart poll compares this field in that mode.
|
|
383
|
+
"worker_id": (
|
|
384
|
+
_worker_instance_id(server)
|
|
385
|
+
if server is not None
|
|
386
|
+
else _PROCESS_INSTANCE_ID
|
|
387
|
+
),
|
|
358
388
|
"started_at": _PROCESS_STARTED_AT,
|
|
359
389
|
"version": version,
|
|
360
390
|
}
|
{ha_mcp_dev-8.3.0.dev2442 → ha_mcp_dev-8.3.0.dev2445}/src/ha_mcp/settings_ui/locales/en.json
RENAMED
|
@@ -184,6 +184,7 @@
|
|
|
184
184
|
"status.stopped_connection": "Stopped (connection dropped)",
|
|
185
185
|
"status.restarting": "Restarting…",
|
|
186
186
|
"status.waiting_addon": "Waiting for app (add-on) to come back online…",
|
|
187
|
+
"status.waiting_server": "Waiting for the HA-MCP server to come back online…",
|
|
187
188
|
"errors.javascript": "JS error: {message} @ {where}",
|
|
188
189
|
"errors.async": "Async error: {message}",
|
|
189
190
|
"errors.network": "Network error: {message}",
|
|
@@ -200,6 +201,7 @@
|
|
|
200
201
|
"errors.restart_failed": "Restart failed",
|
|
201
202
|
"errors.failed_detail": "Failed: {detail}",
|
|
202
203
|
"errors.addon_not_back": "App (add-on) did not come back online. Reload the page manually.",
|
|
204
|
+
"errors.server_not_back": "The HA-MCP server did not come back online. Reload the page manually.",
|
|
203
205
|
"errors.unparseable_suffix": " (response body unparseable)",
|
|
204
206
|
"common.operation_failed": "{operation} failed: {detail}",
|
|
205
207
|
"common.enabled": "enabled",
|
|
@@ -509,6 +509,7 @@ async function applyInfoChrome() {
|
|
|
509
509
|
// worker onto the freshly installed code.
|
|
510
510
|
const rbtn = document.getElementById('restartBtn');
|
|
511
511
|
rbtn.style.display = '';
|
|
512
|
+
restartTargetEmbedded = true;
|
|
512
513
|
rbtn.textContent = t('actions.restart_server', {}, 'Restart HA-MCP Server');
|
|
513
514
|
if (noticeEl) {
|
|
514
515
|
noticeEl.textContent = t(
|
|
@@ -653,6 +654,10 @@ function markRestartRequired() {
|
|
|
653
654
|
// again); otherwise stays true through the restart cycle until the
|
|
654
655
|
// page reloads.
|
|
655
656
|
let restartInProgress = false;
|
|
657
|
+
// True when the restart button drives the embedded (custom component) server
|
|
658
|
+
// rather than the app (add-on) — the wait/give-up copy must name the right
|
|
659
|
+
// thing (issue #2279 feedback: the embedded flow said "app" throughout).
|
|
660
|
+
let restartTargetEmbedded = false;
|
|
656
661
|
|
|
657
662
|
async function _fetchSettingsInfo() {
|
|
658
663
|
// Read ``/api/settings/info`` once; return the parsed JSON or null
|
|
@@ -682,7 +687,8 @@ async function _probeAddonRestarted(previousInstanceId) {
|
|
|
682
687
|
const info = await _fetchSettingsInfo();
|
|
683
688
|
if (info) {
|
|
684
689
|
if (previousInstanceId) {
|
|
685
|
-
|
|
690
|
+
const current = restartTargetEmbedded ? info.worker_id : info.instance_id;
|
|
691
|
+
if (current && current !== previousInstanceId) {
|
|
686
692
|
return true;
|
|
687
693
|
}
|
|
688
694
|
// Same instance_id (or field missing on the response) — keep
|
|
@@ -706,7 +712,9 @@ async function _runRestartReloadCycle(previousInstanceId) {
|
|
|
706
712
|
// instance and we reload before the new one is up.
|
|
707
713
|
btn.textContent = t('status.restarting', {}, 'Restarting…');
|
|
708
714
|
await new Promise(r => setTimeout(r, RESTART_PROBE_INITIAL_GRACE_MS));
|
|
709
|
-
btn.textContent =
|
|
715
|
+
btn.textContent = restartTargetEmbedded
|
|
716
|
+
? t('status.waiting_server', {}, 'Waiting for the HA-MCP server to come back online…')
|
|
717
|
+
: t('status.waiting_addon', {}, 'Waiting for App (add-on) to come back online…');
|
|
710
718
|
const restarted = await _probeAddonRestarted(previousInstanceId);
|
|
711
719
|
if (restarted) {
|
|
712
720
|
window.location.reload();
|
|
@@ -715,7 +723,9 @@ async function _runRestartReloadCycle(previousInstanceId) {
|
|
|
715
723
|
// never actually fired (silent supervisor failure → instance_id
|
|
716
724
|
// never flipped) OR supervisor is genuinely slower than the cap.
|
|
717
725
|
// Surface a clear next-step instead of silently doing nothing.
|
|
718
|
-
btn.textContent =
|
|
726
|
+
btn.textContent = restartTargetEmbedded
|
|
727
|
+
? t('errors.server_not_back', {}, 'The HA-MCP server did not come back online. Reload the page manually.')
|
|
728
|
+
: t('errors.addon_not_back', {}, 'App (add-on) did not come back online. Reload the page manually.');
|
|
719
729
|
btn.disabled = false;
|
|
720
730
|
restartInProgress = false;
|
|
721
731
|
}
|
|
@@ -733,7 +743,12 @@ async function restartAddon() {
|
|
|
733
743
|
// null is fine — the probe degrades to the old "any 200 means up"
|
|
734
744
|
// mode rather than refusing to reload.
|
|
735
745
|
const info = await _fetchSettingsInfo();
|
|
736
|
-
|
|
746
|
+
// Embedded restarts reload the config entry inside the surviving HA
|
|
747
|
+
// process, so instance_id never flips there; worker_id is pinned to the
|
|
748
|
+
// server instance and flips exactly when the reload completes.
|
|
749
|
+
const previousInstanceId = restartTargetEmbedded
|
|
750
|
+
? (info?.worker_id ?? null)
|
|
751
|
+
: (info?.instance_id ?? null);
|
|
737
752
|
try {
|
|
738
753
|
const resp = await fetch('./api/settings/restart', {method: 'POST'});
|
|
739
754
|
if (!resp.ok && resp.status < 500) {
|
|
@@ -769,6 +784,10 @@ async function restartAddon() {
|
|
|
769
784
|
restartChannel.postMessage({
|
|
770
785
|
type: 'restart-initiated',
|
|
771
786
|
previousInstanceId,
|
|
787
|
+
// Receivers may get this before their own init has classified the
|
|
788
|
+
// deployment; without the sender's flag they would compare an
|
|
789
|
+
// embedded worker_id baseline against instance_id and time out.
|
|
790
|
+
targetEmbedded: restartTargetEmbedded,
|
|
772
791
|
});
|
|
773
792
|
}
|
|
774
793
|
await _runRestartReloadCycle(previousInstanceId);
|
|
@@ -787,10 +806,15 @@ if (restartChannel) {
|
|
|
787
806
|
restartInProgress = true;
|
|
788
807
|
const btn = document.getElementById('restartBtn');
|
|
789
808
|
if (btn) btn.disabled = true;
|
|
790
|
-
// Use the originating tab's baseline
|
|
791
|
-
//
|
|
792
|
-
//
|
|
793
|
-
//
|
|
809
|
+
// Use the originating tab's baseline so every tab waits for the
|
|
810
|
+
// SAME identity flip before reloading, and adopt its deployment
|
|
811
|
+
// classification too: this listener can fire before this tab's own
|
|
812
|
+
// init resolved it, and an embedded worker_id baseline compared
|
|
813
|
+
// against instance_id would never flip. Falls back to null →
|
|
814
|
+
// "any 200 = ready" mode if the originator couldn't capture one.
|
|
815
|
+
if (typeof data.targetEmbedded === 'boolean') {
|
|
816
|
+
restartTargetEmbedded = data.targetEmbedded;
|
|
817
|
+
}
|
|
794
818
|
_runRestartReloadCycle(data.previousInstanceId ?? null);
|
|
795
819
|
}
|
|
796
820
|
});
|