ha-mcp-dev 8.3.0.dev2377__tar.gz → 8.3.0.dev2382__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.dev2377/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2382}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/pyproject.toml +1 -1
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/device_control.py +243 -31
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_base.py +13 -1
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_entities.py +232 -40
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_search.py +19 -4
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_service.py +129 -8
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/operation_manager.py +3 -3
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/setup.cfg +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_rich_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/requirements.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/oidc_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/hacs_auto_refresh.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/editing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/redaction.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/renamed_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_config_entries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_identity.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/error_log_parsing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/hacs_registration.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_security_policy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/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.dev2382"
|
|
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 @@ and async operation verification through WebSocket monitoring.
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import json
|
|
10
10
|
import logging
|
|
11
|
+
import math
|
|
11
12
|
import time
|
|
12
13
|
from typing import Any, ClassVar
|
|
13
14
|
|
|
@@ -41,10 +42,23 @@ from .helpers import (
|
|
|
41
42
|
safe_info,
|
|
42
43
|
safe_progress,
|
|
43
44
|
)
|
|
45
|
+
from .tools_service import BulkControlOperation
|
|
44
46
|
from .util_helpers import _SERVICE_TO_STATE
|
|
45
47
|
|
|
46
48
|
logger = logging.getLogger(__name__)
|
|
47
49
|
|
|
50
|
+
# The bulk row contract is declared once, on the advertised transport schema.
|
|
51
|
+
# Reading its keys back here keeps the runtime batch validator from drifting
|
|
52
|
+
# out of sync with what models are told they may send.
|
|
53
|
+
_BULK_OPERATION_KEYS: frozenset[str] = frozenset(BulkControlOperation.__annotations__)
|
|
54
|
+
|
|
55
|
+
# The row shape models get wrong most often is a service-style row, so this
|
|
56
|
+
# leads every suggestion list a rejected bulk batch produces.
|
|
57
|
+
_BULK_ROW_SHAPE_SUGGESTION = (
|
|
58
|
+
"Use entity_id and action (e.g. action='off', not service='turn_off'); "
|
|
59
|
+
"send no domain or service keys and put action data in parameters"
|
|
60
|
+
)
|
|
61
|
+
|
|
48
62
|
# The ha_mcp_tools/bulk_call_service WS command (Phase 3, D5a): the BATCH write
|
|
49
63
|
# capability. When the component advertises ``bulk_call_service`` the consumer
|
|
50
64
|
# resolves every op's domain/service server-side (D6), sends one register-before-fire
|
|
@@ -88,7 +102,7 @@ class DeviceControlTools:
|
|
|
88
102
|
entity_id: str,
|
|
89
103
|
action: str,
|
|
90
104
|
parameters: dict[str, Any] | None = None,
|
|
91
|
-
timeout_seconds:
|
|
105
|
+
timeout_seconds: float = 10,
|
|
92
106
|
validate_first: bool = True,
|
|
93
107
|
) -> dict[str, Any]:
|
|
94
108
|
"""
|
|
@@ -346,8 +360,18 @@ class DeviceControlTools:
|
|
|
346
360
|
"set": "set_temperature",
|
|
347
361
|
}
|
|
348
362
|
|
|
363
|
+
# Silent allowlist: _add_domain_params copies only these keys into the
|
|
364
|
+
# service call and drops everything else without an error, and a domain
|
|
365
|
+
# absent from this table loses every parameter. Extend it when adding
|
|
366
|
+
# support for a new parameter, or the parameter vanishes at dispatch.
|
|
349
367
|
_DOMAIN_PARAMS: ClassVar[dict[str, list[str]]] = {
|
|
350
|
-
"light": [
|
|
368
|
+
"light": [
|
|
369
|
+
"brightness",
|
|
370
|
+
"brightness_pct",
|
|
371
|
+
"color_temp_kelvin",
|
|
372
|
+
"rgb_color",
|
|
373
|
+
"effect",
|
|
374
|
+
],
|
|
351
375
|
"climate": ["temperature", "target_temp_high", "target_temp_low", "hvac_mode"],
|
|
352
376
|
"cover": ["position", "tilt_position"],
|
|
353
377
|
"media_player": ["volume_level", "media_content_id", "media_content_type"],
|
|
@@ -462,15 +486,15 @@ class DeviceControlTools:
|
|
|
462
486
|
return expected if expected else None
|
|
463
487
|
|
|
464
488
|
async def get_device_operation_status(
|
|
465
|
-
self, operation_id: str, timeout_seconds:
|
|
489
|
+
self, operation_id: str, timeout_seconds: float = 10
|
|
466
490
|
) -> dict[str, Any]:
|
|
467
491
|
"""Check status of a device operation, waiting up to ``timeout_seconds`` for completion.
|
|
468
492
|
|
|
469
493
|
Polls the in-memory operation registry (mutated by the WebSocket
|
|
470
|
-
listener as state changes arrive)
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
``control_device_smart``.
|
|
494
|
+
listener as state changes arrive) while the operation is pending, at
|
|
495
|
+
0.2s intervals except for a final shorter poll that lands exactly on
|
|
496
|
+
the deadline. Returns the final structured status —
|
|
497
|
+
completed/failed/timeout/pending — produced by ``control_device_smart``.
|
|
474
498
|
"""
|
|
475
499
|
operation = get_operation_from_memory(operation_id)
|
|
476
500
|
|
|
@@ -495,9 +519,10 @@ class DeviceControlTools:
|
|
|
495
519
|
if operation.status.value == "pending" and timeout_seconds > 0:
|
|
496
520
|
deadline = time.monotonic() + timeout_seconds
|
|
497
521
|
while operation.status.value == "pending":
|
|
498
|
-
|
|
522
|
+
remaining = deadline - time.monotonic()
|
|
523
|
+
if remaining <= 0:
|
|
499
524
|
break
|
|
500
|
-
await asyncio.sleep(0.2)
|
|
525
|
+
await asyncio.sleep(min(0.2, remaining))
|
|
501
526
|
refreshed = get_operation_from_memory(operation_id)
|
|
502
527
|
if refreshed is None:
|
|
503
528
|
raise_tool_error(
|
|
@@ -597,21 +622,128 @@ class DeviceControlTools:
|
|
|
597
622
|
}
|
|
598
623
|
|
|
599
624
|
@staticmethod
|
|
625
|
+
def _normalize_bulk_parameters(
|
|
626
|
+
value: Any,
|
|
627
|
+
) -> tuple[dict[str, Any] | None, ErrorCode | None, str]:
|
|
628
|
+
"""Normalize a present ``parameters`` value and identify invalid ones.
|
|
629
|
+
|
|
630
|
+
Returns ``(parameters, error_code, detail)``. ``detail`` carries the
|
|
631
|
+
decoder's own reason and offset for a JSON failure — without it the
|
|
632
|
+
caller can only say "invalid JSON", which does not tell the sender
|
|
633
|
+
where its string went wrong. It is empty for every other outcome.
|
|
634
|
+
"""
|
|
635
|
+
if isinstance(value, str):
|
|
636
|
+
try:
|
|
637
|
+
value = json.loads(value)
|
|
638
|
+
except json.JSONDecodeError as exc:
|
|
639
|
+
return (
|
|
640
|
+
None,
|
|
641
|
+
ErrorCode.VALIDATION_INVALID_JSON,
|
|
642
|
+
f"{exc.msg} at position {exc.pos}",
|
|
643
|
+
)
|
|
644
|
+
except RecursionError:
|
|
645
|
+
# json.loads recurses per nesting level; a pathologically
|
|
646
|
+
# nested string must skip the row, not abort the batch.
|
|
647
|
+
return (
|
|
648
|
+
None,
|
|
649
|
+
ErrorCode.VALIDATION_INVALID_JSON,
|
|
650
|
+
"JSON nested too deeply",
|
|
651
|
+
)
|
|
652
|
+
if not isinstance(value, dict):
|
|
653
|
+
# A present-but-null value is malformed, same as timeout_seconds
|
|
654
|
+
# and validate_first; an absent key never reaches this helper.
|
|
655
|
+
return None, ErrorCode.VALIDATION_INVALID_PARAMETER, ""
|
|
656
|
+
return value, None, ""
|
|
657
|
+
|
|
658
|
+
@staticmethod
|
|
659
|
+
def _normalize_bulk_timeout(value: Any) -> float | None:
|
|
660
|
+
"""Normalize a non-negative finite timeout, or ``None`` when invalid.
|
|
661
|
+
|
|
662
|
+
Bools are rejected before ``float()`` sees them: ``float(True)`` is 1.0,
|
|
663
|
+
which would silently accept ``timeout_seconds: true`` as a one-second
|
|
664
|
+
wait. Strings are rejected for the same reason — the advertised schema
|
|
665
|
+
is ``strict``, so coercing ``"10"`` here would silently accept what the
|
|
666
|
+
schema tells the model is invalid. ``None`` is invalid here too — the
|
|
667
|
+
caller only calls this when the key is present, and a present-but-null
|
|
668
|
+
timeout is a malformed row.
|
|
669
|
+
"""
|
|
670
|
+
if isinstance(value, (bool, str)) or value is None:
|
|
671
|
+
return None
|
|
672
|
+
try:
|
|
673
|
+
timeout = float(value)
|
|
674
|
+
except (TypeError, ValueError, OverflowError):
|
|
675
|
+
# OverflowError: an int too large for float must skip the row,
|
|
676
|
+
# not abort the batch through the generic exception handler.
|
|
677
|
+
return None
|
|
678
|
+
return timeout if math.isfinite(timeout) and timeout >= 0 else None
|
|
679
|
+
|
|
680
|
+
@staticmethod
|
|
681
|
+
def _normalized_bulk_operation(
|
|
682
|
+
operation: dict[str, Any],
|
|
683
|
+
parameters: dict[str, Any] | None,
|
|
684
|
+
timeout: float | None,
|
|
685
|
+
) -> dict[str, Any]:
|
|
686
|
+
"""Return one normalized valid bulk operation."""
|
|
687
|
+
normalized = dict(operation)
|
|
688
|
+
if parameters is not None:
|
|
689
|
+
normalized["parameters"] = parameters
|
|
690
|
+
if timeout is not None:
|
|
691
|
+
normalized["timeout_seconds"] = timeout
|
|
692
|
+
return normalized
|
|
693
|
+
|
|
694
|
+
@staticmethod
|
|
695
|
+
def _skip_bulk_operation(
|
|
696
|
+
skipped_operations: list[dict[str, Any]],
|
|
697
|
+
op: Any,
|
|
698
|
+
code: ErrorCode,
|
|
699
|
+
error: str,
|
|
700
|
+
context: dict[str, Any],
|
|
701
|
+
suggestions: list[str] | None = None,
|
|
702
|
+
) -> None:
|
|
703
|
+
"""Log one rejected bulk row and record its structured skip entry.
|
|
704
|
+
|
|
705
|
+
``create_error_response`` lifts every ``context`` key to the top level,
|
|
706
|
+
so ``index`` needs no separate write; ``operation`` is not a context
|
|
707
|
+
key and is attached here so callers can see the row they sent.
|
|
708
|
+
"""
|
|
709
|
+
logger.warning(f"Bulk control: {error}")
|
|
710
|
+
err_response = create_error_response(
|
|
711
|
+
code, error, suggestions=suggestions, context=context
|
|
712
|
+
)
|
|
713
|
+
err_response["operation"] = op
|
|
714
|
+
skipped_operations.append(err_response)
|
|
715
|
+
|
|
716
|
+
@classmethod
|
|
600
717
|
def _validate_bulk_operations(
|
|
601
|
-
|
|
718
|
+
cls,
|
|
719
|
+
operations: list[Any],
|
|
602
720
|
skipped_operations: list[dict[str, Any]],
|
|
603
721
|
) -> list[tuple[int, dict[str, Any], str, str]]:
|
|
604
722
|
valid: list[tuple[int, dict[str, Any], str, str]] = []
|
|
605
723
|
for i, op in enumerate(operations):
|
|
606
724
|
if not isinstance(op, dict):
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
ErrorCode.
|
|
725
|
+
cls._skip_bulk_operation(
|
|
726
|
+
skipped_operations,
|
|
727
|
+
op,
|
|
728
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
729
|
+
f"Operation at index {i} is not a dict: {type(op).__name__}",
|
|
730
|
+
{"index": i},
|
|
731
|
+
)
|
|
732
|
+
continue
|
|
733
|
+
|
|
734
|
+
# str() before sorting: a direct Python caller can pass non-string
|
|
735
|
+
# keys, and mixed key types make sorted()/join() raise mid-batch.
|
|
736
|
+
obsolete_keys = sorted(str(key) for key in set(op) - _BULK_OPERATION_KEYS)
|
|
737
|
+
if obsolete_keys:
|
|
738
|
+
key_list = ", ".join(obsolete_keys)
|
|
739
|
+
cls._skip_bulk_operation(
|
|
740
|
+
skipped_operations,
|
|
741
|
+
op,
|
|
742
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
743
|
+
f"Operation at index {i} contains unsupported key(s): {key_list}",
|
|
744
|
+
{"index": i, "unsupported_keys": obsolete_keys},
|
|
745
|
+
suggestions=[_BULK_ROW_SHAPE_SUGGESTION],
|
|
611
746
|
)
|
|
612
|
-
err_response["index"] = i
|
|
613
|
-
err_response["operation"] = op
|
|
614
|
-
skipped_operations.append(err_response)
|
|
615
747
|
continue
|
|
616
748
|
|
|
617
749
|
entity_id = op.get("entity_id")
|
|
@@ -619,21 +751,80 @@ class DeviceControlTools:
|
|
|
619
751
|
missing = [f for f in ("entity_id", "action") if not op.get(f)]
|
|
620
752
|
|
|
621
753
|
if missing:
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
ErrorCode.VALIDATION_MISSING_PARAMETER,
|
|
754
|
+
cls._skip_bulk_operation(
|
|
755
|
+
skipped_operations,
|
|
756
|
+
op,
|
|
757
|
+
ErrorCode.VALIDATION_MISSING_PARAMETER,
|
|
758
|
+
f"Operation at index {i} missing required fields: "
|
|
759
|
+
f"{', '.join(missing)}",
|
|
760
|
+
{"index": i},
|
|
626
761
|
)
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
762
|
+
continue
|
|
763
|
+
|
|
764
|
+
if not isinstance(entity_id, str) or not isinstance(action, str):
|
|
765
|
+
cls._skip_bulk_operation(
|
|
766
|
+
skipped_operations,
|
|
767
|
+
op,
|
|
768
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
769
|
+
f"Operation at index {i} requires string entity_id and "
|
|
770
|
+
"action fields",
|
|
771
|
+
{"index": i},
|
|
772
|
+
)
|
|
773
|
+
continue
|
|
774
|
+
|
|
775
|
+
parameters, parameter_error, parameter_detail = (
|
|
776
|
+
cls._normalize_bulk_parameters(op["parameters"])
|
|
777
|
+
if "parameters" in op
|
|
778
|
+
else (None, None, "")
|
|
779
|
+
)
|
|
780
|
+
if parameter_error is not None:
|
|
781
|
+
parameter_message = (
|
|
782
|
+
f"Operation at index {i} has invalid JSON parameters: "
|
|
783
|
+
f"{parameter_detail}"
|
|
784
|
+
if parameter_error == ErrorCode.VALIDATION_INVALID_JSON
|
|
785
|
+
else f"Operation at index {i} parameters must be a JSON object"
|
|
786
|
+
)
|
|
787
|
+
cls._skip_bulk_operation(
|
|
788
|
+
skipped_operations,
|
|
789
|
+
op,
|
|
790
|
+
parameter_error,
|
|
791
|
+
parameter_message,
|
|
792
|
+
{"index": i},
|
|
793
|
+
)
|
|
794
|
+
continue
|
|
795
|
+
|
|
796
|
+
timeout: float | None = None
|
|
797
|
+
if "timeout_seconds" in op:
|
|
798
|
+
timeout = cls._normalize_bulk_timeout(op["timeout_seconds"])
|
|
799
|
+
if timeout is None:
|
|
800
|
+
cls._skip_bulk_operation(
|
|
801
|
+
skipped_operations,
|
|
802
|
+
op,
|
|
803
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
804
|
+
f"Operation at index {i} timeout_seconds must be a "
|
|
805
|
+
"non-negative number",
|
|
806
|
+
{"index": i},
|
|
807
|
+
)
|
|
808
|
+
continue
|
|
809
|
+
|
|
810
|
+
if "validate_first" in op and not isinstance(op["validate_first"], bool):
|
|
811
|
+
cls._skip_bulk_operation(
|
|
812
|
+
skipped_operations,
|
|
813
|
+
op,
|
|
814
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
815
|
+
f"Operation at index {i} validate_first must be a boolean",
|
|
816
|
+
{"index": i},
|
|
817
|
+
)
|
|
818
|
+
continue
|
|
819
|
+
|
|
820
|
+
normalized_op = cls._normalized_bulk_operation(op, parameters, timeout)
|
|
821
|
+
valid.append((i, normalized_op, entity_id, action))
|
|
822
|
+
|
|
632
823
|
return valid
|
|
633
824
|
|
|
634
825
|
async def bulk_device_control(
|
|
635
826
|
self,
|
|
636
|
-
operations: list[
|
|
827
|
+
operations: list[Any],
|
|
637
828
|
parallel: bool = True,
|
|
638
829
|
ctx: Context | None = None,
|
|
639
830
|
) -> dict[str, Any]:
|
|
@@ -672,6 +863,26 @@ class DeviceControlTools:
|
|
|
672
863
|
f"{len(skipped_operations)} skipped, "
|
|
673
864
|
f"mode={'parallel' if parallel else 'sequential'}",
|
|
674
865
|
)
|
|
866
|
+
if not valid_operations:
|
|
867
|
+
# Nothing dispatched, so nothing to fail soft for: the batch
|
|
868
|
+
# carve-out from "all tool-level failures raise" exists to
|
|
869
|
+
# protect successful siblings, and there are none. Raising
|
|
870
|
+
# before the component probe keeps the no-dispatch guarantee.
|
|
871
|
+
raise_tool_error(
|
|
872
|
+
create_error_response(
|
|
873
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
874
|
+
f"All {len(operations)} operation(s) failed validation; "
|
|
875
|
+
"nothing was dispatched",
|
|
876
|
+
suggestions=[
|
|
877
|
+
_BULK_ROW_SHAPE_SUGGESTION,
|
|
878
|
+
"Each operation requires 'entity_id' and 'action'",
|
|
879
|
+
"Check skipped_details for the per-operation reason",
|
|
880
|
+
"Example: {'entity_id': 'light.living_room', "
|
|
881
|
+
+ "'action': 'on'}",
|
|
882
|
+
],
|
|
883
|
+
context={"skipped_details": skipped_operations},
|
|
884
|
+
)
|
|
885
|
+
)
|
|
675
886
|
|
|
676
887
|
# Route through the component's bulk_call_service capability when
|
|
677
888
|
# advertised (D5a): one register-before-fire batch frame confirms every
|
|
@@ -904,7 +1115,7 @@ class DeviceControlTools:
|
|
|
904
1115
|
|
|
905
1116
|
async def _bulk_via_component(
|
|
906
1117
|
self,
|
|
907
|
-
operations: list[
|
|
1118
|
+
operations: list[Any],
|
|
908
1119
|
valid_operations: list[tuple[int, dict[str, Any], str, str]],
|
|
909
1120
|
skipped_operations: list[dict[str, Any]],
|
|
910
1121
|
parallel: bool,
|
|
@@ -1111,7 +1322,7 @@ class DeviceControlTools:
|
|
|
1111
1322
|
|
|
1112
1323
|
def _build_ambiguous_bulk_response(
|
|
1113
1324
|
self,
|
|
1114
|
-
operations: list[
|
|
1325
|
+
operations: list[Any],
|
|
1115
1326
|
valid_operations: list[tuple[int, dict[str, Any], str, str]],
|
|
1116
1327
|
rows: list[dict[str, Any]],
|
|
1117
1328
|
skipped_operations: list[dict[str, Any]],
|
|
@@ -1246,7 +1457,7 @@ class DeviceControlTools:
|
|
|
1246
1457
|
|
|
1247
1458
|
def _build_bulk_response(
|
|
1248
1459
|
self,
|
|
1249
|
-
operations: list[
|
|
1460
|
+
operations: list[Any],
|
|
1250
1461
|
results: list[dict[str, Any]],
|
|
1251
1462
|
operation_ids: list[str],
|
|
1252
1463
|
skipped_operations: list[dict[str, Any]],
|
|
@@ -1284,6 +1495,7 @@ class DeviceControlTools:
|
|
|
1284
1495
|
if skipped_operations:
|
|
1285
1496
|
response["skipped_details"] = skipped_operations
|
|
1286
1497
|
response["suggestions"] = [
|
|
1498
|
+
_BULK_ROW_SHAPE_SUGGESTION,
|
|
1287
1499
|
"Some operations were skipped due to validation errors",
|
|
1288
1500
|
"Each operation requires 'entity_id' and 'action' fields",
|
|
1289
1501
|
"Check skipped_details for specific errors",
|
|
@@ -1293,7 +1505,7 @@ class DeviceControlTools:
|
|
|
1293
1505
|
return response
|
|
1294
1506
|
|
|
1295
1507
|
async def get_bulk_operation_status(
|
|
1296
|
-
self, operation_ids: list[str], timeout_seconds:
|
|
1508
|
+
self, operation_ids: list[str], timeout_seconds: float = 10
|
|
1297
1509
|
) -> dict[str, Any]:
|
|
1298
1510
|
"""
|
|
1299
1511
|
Check status of multiple operations.
|
{ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_base.py
RENAMED
|
@@ -40,7 +40,19 @@ class _SearchBase:
|
|
|
40
40
|
logger.debug(f"Could not fetch {label}: {result}")
|
|
41
41
|
cause = str(result) or type(result).__name__
|
|
42
42
|
elif isinstance(result, dict) and result.get("success"):
|
|
43
|
-
|
|
43
|
+
# No default for a missing key: a legitimate empty registry is
|
|
44
|
+
# "result": [], so an absent result is a malformed envelope and
|
|
45
|
+
# must degrade loudly, not read as available-and-empty.
|
|
46
|
+
payload = result.get("result")
|
|
47
|
+
if isinstance(payload, list):
|
|
48
|
+
registry = payload
|
|
49
|
+
elif payload is None and "result" not in result:
|
|
50
|
+
cause = "malformed result payload: result key missing"
|
|
51
|
+
else:
|
|
52
|
+
cause = (
|
|
53
|
+
"malformed result payload: expected list, "
|
|
54
|
+
f"got {type(payload).__name__}"
|
|
55
|
+
)
|
|
44
56
|
elif isinstance(result, dict):
|
|
45
57
|
cause = str(result.get("error") or "request failed")
|
|
46
58
|
else:
|