ha-mcp-dev 8.3.0.dev2444__tar.gz → 8.3.0.dev2447__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ha_mcp_dev-8.3.0.dev2444/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2447}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/pyproject.toml +1 -1
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/rest_client.py +262 -50
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +1 -1
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +1 -1
- ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/resources/skills-vendor/AGENTS.md +40 -0
- ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/resources/skills-vendor/CLAUDE.md +40 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/README.md +1 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +14 -8
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +30 -0
- ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/backups.md +128 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +4 -1
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +1 -1
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/server.py +192 -2
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_server.py +30 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/en.json +4 -1
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/settings.js +158 -44
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/backup.py +18 -3
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/error_log_parsing.py +208 -4
- ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/log_common.py +199 -0
- ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/log_sources.py +764 -0
- ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/log_sources_supervisor.py +326 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/registry.py +12 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_bug_report.py +15 -6
- ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/tools_logs.py +296 -0
- ha_mcp_dev-8.3.0.dev2447/src/ha_mcp/tools/tools_utility.py +277 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/skill_loader.py +22 -4
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/SOURCES.txt +5 -0
- ha_mcp_dev-8.3.0.dev2444/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -57
- ha_mcp_dev-8.3.0.dev2444/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -57
- ha_mcp_dev-8.3.0.dev2444/src/ha_mcp/tools/tools_utility.py +0 -1600
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/setup.cfg +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_rich_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/requirements.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_vendor/websockets/version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/auth/oidc_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/hacs_auto_refresh.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/log_filters.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/editing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/redaction.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/renamed_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/bulk_selector.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_config_entries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_identity.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/hacs_registration.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_security_policy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/entity_membership.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/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.dev2447"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# Upper bound widened to <3.15 so the package installs on Home Assistant's
|
|
@@ -8,6 +8,7 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
import ssl
|
|
10
10
|
import time
|
|
11
|
+
from collections.abc import Awaitable, Callable
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from typing import Any, NoReturn
|
|
13
14
|
|
|
@@ -52,10 +53,23 @@ logger = logging.getLogger(__name__)
|
|
|
52
53
|
_RETRYABLE_GATEWAY_STATUS = frozenset({502, 503, 504})
|
|
53
54
|
_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
|
|
54
55
|
_MAX_REQUEST_ATTEMPTS = 3
|
|
55
|
-
#
|
|
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
|
"""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "home-assistant-skills",
|
|
3
3
|
"description": "Agent skills for Home Assistant: best practices for automations, helpers, templates, device control, and dashboards.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"repository": "https://github.com/homeassistant-ai/skills",
|
|
6
6
|
"license": "MIT"
|
|
7
7
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Skill Format
|
|
6
|
+
|
|
7
|
+
Every skill is a `SKILL.md` file with YAML frontmatter:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
---
|
|
11
|
+
name: skill-name-here
|
|
12
|
+
description: >
|
|
13
|
+
When to activate this skill and what symptoms it addresses.
|
|
14
|
+
---
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Full authoring constraints: `CONTRIBUTING.md`. The one CI cannot catch:
|
|
18
|
+
- `metadata.version` must be `0` on new skills — do not edit manually; CI assigns the real version on merge
|
|
19
|
+
|
|
20
|
+
## Skill Authoring Principles
|
|
21
|
+
|
|
22
|
+
- **Context window conservation** — only include domain-specific knowledge that agents lack; omit general programming advice
|
|
23
|
+
- **Conciseness** — provide patterns and quick-reference tables, not tutorials
|
|
24
|
+
- **Consistent terminology** — one term per concept throughout a skill
|
|
25
|
+
- **Symptom-based triggering** — the `description` frontmatter should describe observable agent behaviors that signal the skill is needed
|
|
26
|
+
- **No tool names** — reference HA REST APIs and concepts, never specific MCP tool names (e.g. `ha_rename_entity`); tool names vary by agent setup
|
|
27
|
+
|
|
28
|
+
## Validation
|
|
29
|
+
|
|
30
|
+
To validate locally:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uvx skills-ref validate skills/<skill-name>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Reviewing Skill PRs
|
|
37
|
+
|
|
38
|
+
- Judge prose as agent-consumed context, not human docs — the Skill Authoring Principles above are the review bar (e.g. an operator→result lookup table beats narrative bullets, because agents land here holding one case to resolve)
|
|
39
|
+
- Skills make version-pinned claims about HA behavior — verify against source at the release tag: `gh api repos/home-assistant/core/contents/<path>?ref=2026.7.4 --jq .content | base64 -d`
|
|
40
|
+
- Community PRs come from forks: base-repo `?ref=<pr-branch>` 404s; get the fork with `gh pr view <pr> --json headRepository` and fetch files from there
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Skill Format
|
|
6
|
+
|
|
7
|
+
Every skill is a `SKILL.md` file with YAML frontmatter:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
---
|
|
11
|
+
name: skill-name-here
|
|
12
|
+
description: >
|
|
13
|
+
When to activate this skill and what symptoms it addresses.
|
|
14
|
+
---
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Full authoring constraints: `CONTRIBUTING.md`. The one CI cannot catch:
|
|
18
|
+
- `metadata.version` must be `0` on new skills — do not edit manually; CI assigns the real version on merge
|
|
19
|
+
|
|
20
|
+
## Skill Authoring Principles
|
|
21
|
+
|
|
22
|
+
- **Context window conservation** — only include domain-specific knowledge that agents lack; omit general programming advice
|
|
23
|
+
- **Conciseness** — provide patterns and quick-reference tables, not tutorials
|
|
24
|
+
- **Consistent terminology** — one term per concept throughout a skill
|
|
25
|
+
- **Symptom-based triggering** — the `description` frontmatter should describe observable agent behaviors that signal the skill is needed
|
|
26
|
+
- **No tool names** — reference HA REST APIs and concepts, never specific MCP tool names (e.g. `ha_rename_entity`); tool names vary by agent setup
|
|
27
|
+
|
|
28
|
+
## Validation
|
|
29
|
+
|
|
30
|
+
To validate locally:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uvx skills-ref validate skills/<skill-name>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Reviewing Skill PRs
|
|
37
|
+
|
|
38
|
+
- Judge prose as agent-consumed context, not human docs — the Skill Authoring Principles above are the review bar (e.g. an operator→result lookup table beats narrative bullets, because agents land here holding one case to resolve)
|
|
39
|
+
- Skills make version-pinned claims about HA behavior — verify against source at the release tag: `gh api repos/home-assistant/core/contents/<path>?ref=2026.7.4 --jq .content | base64 -d`
|
|
40
|
+
- Community PRs come from forks: base-repo `?ref=<pr-branch>` 404s; get the fork with `gh pr view <pr> --json headRepository` and fetch files from there
|
{ha_mcp_dev-8.3.0.dev2444 → ha_mcp_dev-8.3.0.dev2447}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
@@ -76,6 +76,7 @@ The `home-assistant-best-practices` skill includes:
|
|
|
76
76
|
| `references/examples.yaml` | Compound examples combining multiple best practices |
|
|
77
77
|
| `references/appdaemon.md` | AppDaemon apps: when to use vs. native HA, app structure, service calls, scheduling, error handling, safe refactoring impact |
|
|
78
78
|
| `references/blueprint-guide.md` | Blueprint authoring: metadata & `source_url`, inputs & selectors, `target` vs `entity`, defaults, `!input` templating, versioning |
|
|
79
|
+
| `references/backups.md` | Full instance backups vs rolling one object back, when an operation needs a backup first, what an archive contains, encryption keys, restore verification, backup deletion |
|
|
79
80
|
|
|
80
81
|
## Contributing
|
|
81
82
|
|
|
@@ -5,14 +5,14 @@ description: >
|
|
|
5
5
|
|
|
6
6
|
TRIGGER THIS SKILL WHEN:
|
|
7
7
|
- Creating or editing automations, scripts, scenes, or dashboards
|
|
8
|
-
- Choosing
|
|
9
|
-
- Restructuring triggers, conditions, or
|
|
10
|
-
-
|
|
8
|
+
- Choosing template sensors vs built-in helpers
|
|
9
|
+
- Restructuring triggers, conditions, or modes
|
|
10
|
+
- Zigbee button/remote automations
|
|
11
11
|
- Renaming entities or migrating device_id to entity_id
|
|
12
|
-
- Configuring dashboard cards or selecting helpers
|
|
13
12
|
- Looking up card types or domain docs
|
|
14
|
-
-
|
|
15
|
-
- Authoring
|
|
13
|
+
- AppDaemon apps
|
|
14
|
+
- Authoring reusable Blueprints
|
|
15
|
+
- About to delete a backup, restore a backup, or upgrade Core or the OS
|
|
16
16
|
|
|
17
17
|
SYMPTOMS:
|
|
18
18
|
- Agent uses Jinja2 templates where native options exist
|
|
@@ -22,9 +22,10 @@ description: >
|
|
|
22
22
|
- Agent hard-codes values or uses raw sensor over helper
|
|
23
23
|
- Agent edits .storage, writes YAML, or generates YAML snippets
|
|
24
24
|
- Agent tells user to edit configuration.yaml for UI integrations
|
|
25
|
-
- Agent hardcodes entities
|
|
25
|
+
- Agent hardcodes Blueprint entities or uses free-text over a selector
|
|
26
|
+
- Agent changes existing state with no recovery path
|
|
26
27
|
metadata:
|
|
27
|
-
version:
|
|
28
|
+
version: 18
|
|
28
29
|
---
|
|
29
30
|
|
|
30
31
|
# Home Assistant Best Practices
|
|
@@ -122,6 +123,10 @@ See `references/device-control.md#zigbee-buttonremote-patterns`.
|
|
|
122
123
|
| Free-text input for an entity/device in a Blueprint | Typed `entity`/`target`/`device` selector | Text lets typos through and fails silently; selectors validate the choice | `references/blueprint-guide.md#inputs-and-selectors` |
|
|
123
124
|
| `!input` used directly inside a template | Bind it to a `variables:` entry, use the variable | `!input` is a YAML tag, not a template value — the template errors or ignores it | `references/blueprint-guide.md#referencing-inputs-input-and-templating` |
|
|
124
125
|
| Publishing a Blueprint without `source_url` | Set `source_url` to the file's canonical URL | Without it users can't re-import updates and sharing is awkward | `references/blueprint-guide.md#blueprint-metadata` |
|
|
126
|
+
| Full instance restore to undo one automation/script/scene/dashboard/helper edit | Roll that object back — write its previous definition back through the config API | A full restore reverts every unrelated change made since the backup and restarts HA | `references/backups.md#which-recovery-path` |
|
|
127
|
+
| Deleting a device/entity/integration, or upgrading Core, without a preceding backup | Take the full backup *before* the operation | Registry deletions can't be undone by writing the old value back, and a backup taken afterward captures the damage | `references/backups.md#when-a-full-backup-earns-its-cost` |
|
|
128
|
+
| Restoring a backup, deleting a backup, or upgrading Core or the OS without explicit user confirmation | Ask, name the concrete effect, and wait for an answer — every time, backup or not | A full restore discards everything since the archive for all restored parts and restarts HA; a Supervisor partial restore overwrites only the selected archive parts; deletion destroys a recovery point; a Core/OS upgrade is high-impact and its recovery path IS the pre-upgrade backup | `references/backups.md` |
|
|
129
|
+
| Calling a service "reversible" without naming its inverse | Treat an operation as reversible only when you can name and target its exact inverse | `vacuum.send_command`, `*.press` and `notify.*` have none; and an exact inverse still doesn't undo the consequence — re-locking a door doesn't un-expose the house | `references/backups.md#when-a-full-backup-earns-its-cost` |
|
|
125
130
|
|
|
126
131
|
---
|
|
127
132
|
|
|
@@ -144,3 +149,4 @@ Read these when you need detailed information:
|
|
|
144
149
|
| `references/examples.yaml` | Need compound examples combining multiple best practices | — |
|
|
145
150
|
| `references/appdaemon.md` | AppDaemon apps: when to use vs. native HA, app structure, service calls, scheduling, error handling, safe refactoring impact | — |
|
|
146
151
|
| `references/blueprint-guide.md` | Authoring reusable blueprints: metadata & `source_url`, inputs & selectors, `target` vs `entity`, defaults, input sections, `!input` templating, versioning | `#when-to-author-a-blueprint`, `#blueprint-metadata`, `#inputs-and-selectors`, `#target-selector-vs-entity-selector`, `#defaults`, `#input-sections`, `#referencing-inputs-input-and-templating`, `#versioning-and-updates`, `#common-pitfalls` |
|
|
152
|
+
| `references/backups.md` | Deciding whether an operation needs a backup first; choosing between a full restore, a partial restore, and rolling one object back; what an archive actually contains; encryption keys and the emergency kit; restore verification; what HA does and does not protect when deleting a backup | `#which-recovery-path`, `#when-a-full-backup-earns-its-cost`, `#what-a-full-backup-contains`, `#restoring-consequences-and-verification`, `#deleting-backups`, `#common-pitfalls` |
|
|
@@ -927,6 +927,36 @@ variables:
|
|
|
927
927
|
|
|
928
928
|
`trigger_variables:` is a separate top-level key evaluated **before** triggers fire — it supports **limited templates only** (no `states()`/`state_attr()`), mainly for passing a blueprint `!input` into trigger options. Don't put state-based templates there.
|
|
929
929
|
|
|
930
|
+
### Keys render in order, one at a time
|
|
931
|
+
|
|
932
|
+
A `variables:` block renders one key at a time, each rendered result feeding the context for the next. A key that reads a name declared **further down the same block** reads it while it is still undefined.
|
|
933
|
+
|
|
934
|
+
```yaml
|
|
935
|
+
# AVOID — `total` is still undefined when `msg` renders
|
|
936
|
+
variables:
|
|
937
|
+
msg: "Total: {{ total }}"
|
|
938
|
+
total: "{{ states('sensor.a') | float(0) + states('sensor.b') | float(0) }}"
|
|
939
|
+
|
|
940
|
+
# CORRECT — declare before use
|
|
941
|
+
variables:
|
|
942
|
+
total: "{{ states('sensor.a') | float(0) + states('sensor.b') | float(0) }}"
|
|
943
|
+
msg: "Total: {{ total }}"
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
What the forward read costs depends on how the template uses the name (behavior as of 2026.7):
|
|
947
|
+
|
|
948
|
+
| Use of the undefined name | Result |
|
|
949
|
+
|---|---|
|
|
950
|
+
| `x.attr`, `x['k']`, arithmetic, `<`/`>`/`<=`/`>=`, `\| int`, `\| float` | Raises `UndefinedError`, logs ERROR — **aborts the run** |
|
|
951
|
+
| `{{ x }}`, `{% if x %}`, `{% for i in x %}`, `'p' ~ x` | Empty and falsy, logs WARNING — runs on into the else branch |
|
|
952
|
+
| `x \| length`, `x == y`, `x != y` | `0` / `False` / `True` — **no log and no trace entry** |
|
|
953
|
+
|
|
954
|
+
For the aborting row, [`continue_on_error:`](#continue-on-error) is no escape — it is an action option, a top-level block is not an action, and on a mid-sequence step it cannot suppress a render error. A default like `| int(0)` does not save it either: the default covers an unconvertible value, not an undefined name. In the silent row, watch `!=`: `{% if later != 'x' %}` on a name that is not defined yet takes the **then** branch, leaving nothing behind to explain why.
|
|
955
|
+
|
|
956
|
+
Not a problem when the name also exists in an enclosing scope — the reference then resolves outward to that value. The two placements diverge on that collision, though: a top-level `variables:` block **skips** a key already present in the incoming scope, so the incoming value stands and the block's own definition never takes effect for that run, while a mid-sequence `- variables:` step renders and **overwrites** it. Staging a computation across consecutive `variables:` steps is therefore mid-sequence only.
|
|
957
|
+
|
|
958
|
+
When an automation declares both `trigger_variables:` and `variables:`, the two render as one sequential mapping with `trigger_variables:` first, so a `variables:` key may read a `trigger_variables:` key but not the reverse. That holds for names declared in only one of the two: a name in both merges into a single entry that keeps the `trigger_variables:` position but takes the `variables:` template, so it renders early — before any `trigger_variables:` key declared after it. The separate attach-time render noted above happens before any `variables:` key exists.
|
|
959
|
+
|
|
930
960
|
---
|
|
931
961
|
|
|
932
962
|
## Capturing Action Responses
|