ha-mcp-dev 7.12.0.dev1834__tar.gz → 7.12.0.dev1837__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-7.12.0.dev1834/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.12.0.dev1837}/PKG-INFO +1 -1
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/pyproject.toml +70 -4
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/best_practice_checker.py +201 -106
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_calendar.py +116 -80
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_config_dashboards.py +1323 -949
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_mcp_component.py +80 -67
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_resources.py +42 -36
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_search.py +76 -41
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_service.py +100 -48
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_utility.py +138 -94
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/util_helpers.py +80 -58
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/LICENSE +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/README.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/setup.cfg +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/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 = "7.12.0.
|
|
7
|
+
version = "7.12.0.dev1837"
|
|
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
|
|
@@ -129,6 +129,7 @@ select = [
|
|
|
129
129
|
"I", # isort
|
|
130
130
|
"B", # flake8-bugbear
|
|
131
131
|
"C4", # flake8-comprehensions
|
|
132
|
+
"C90", # mccabe complexity
|
|
132
133
|
"UP", # pyupgrade
|
|
133
134
|
"RUF", # ruff-specific rules
|
|
134
135
|
"PIE", # misc lints
|
|
@@ -159,9 +160,74 @@ ignore = [
|
|
|
159
160
|
[tool.ruff.lint.per-file-ignores]
|
|
160
161
|
"__init__.py" = ["F401"]
|
|
161
162
|
"tests/**/*" = ["E501", "B011"]
|
|
162
|
-
# C901
|
|
163
|
-
#
|
|
164
|
-
|
|
163
|
+
# C901 (mccabe complexity) grandfathered debt: newly enabled rule, these files
|
|
164
|
+
# already exceeded the threshold. Remove a line once its functions are
|
|
165
|
+
# refactored below threshold -- do not add new files here.
|
|
166
|
+
"custom_components/ha_mcp_tools/__init__.py" = ["C901"]
|
|
167
|
+
"custom_components/ha_mcp_tools/mcp_webhook.py" = ["C901"]
|
|
168
|
+
"homeassistant-addon/start.py" = ["C901"]
|
|
169
|
+
"homeassistant-addon-webhook-proxy-dev/mcp_proxy_dev/__init__.py" = ["C901"]
|
|
170
|
+
"homeassistant-addon-webhook-proxy-dev/start.py" = ["C901"]
|
|
171
|
+
"homeassistant-addon-webhook-proxy/mcp_proxy/__init__.py" = ["C901"]
|
|
172
|
+
"homeassistant-addon-webhook-proxy/start.py" = ["C901"]
|
|
173
|
+
"packaging/mcpb/generate_manifest.py" = ["C901"]
|
|
174
|
+
"scripts/extract_tools.py" = ["C901"]
|
|
175
|
+
"src/ha_mcp/backup_manager.py" = ["C901"]
|
|
176
|
+
"src/ha_mcp/client/rest_client.py" = ["C901"]
|
|
177
|
+
"src/ha_mcp/config.py" = ["C901"]
|
|
178
|
+
"src/ha_mcp/policy/evaluator.py" = ["C901"]
|
|
179
|
+
"src/ha_mcp/policy/handlers.py" = ["C901"]
|
|
180
|
+
"src/ha_mcp/server.py" = ["C901"]
|
|
181
|
+
"src/ha_mcp/settings_ui/__init__.py" = ["C901"]
|
|
182
|
+
"src/ha_mcp/tools/auto_backup.py" = ["C901"]
|
|
183
|
+
"src/ha_mcp/tools/backup.py" = ["C901"]
|
|
184
|
+
"src/ha_mcp/tools/radio/matter.py" = ["C901"]
|
|
185
|
+
"src/ha_mcp/tools/radio/zigbee.py" = ["C901"]
|
|
186
|
+
"src/ha_mcp/tools/radio/zwave.py" = ["C901"]
|
|
187
|
+
"src/ha_mcp/tools/reference_validator.py" = ["C901"]
|
|
188
|
+
"src/ha_mcp/tools/tools_addons.py" = ["C901"]
|
|
189
|
+
"src/ha_mcp/tools/tools_areas.py" = ["C901"]
|
|
190
|
+
"src/ha_mcp/tools/tools_code.py" = ["C901"]
|
|
191
|
+
"src/ha_mcp/tools/tools_config_automations.py" = ["C901"]
|
|
192
|
+
"src/ha_mcp/tools/tools_config_scenes.py" = ["C901"]
|
|
193
|
+
"src/ha_mcp/tools/tools_config_scripts.py" = ["C901"]
|
|
194
|
+
"src/ha_mcp/tools/tools_dev.py" = ["C901"]
|
|
195
|
+
"src/ha_mcp/tools/tools_energy.py" = ["C901"]
|
|
196
|
+
"src/ha_mcp/tools/tools_hacs.py" = ["C901"]
|
|
197
|
+
"src/ha_mcp/tools/tools_history.py" = ["C901"]
|
|
198
|
+
"src/ha_mcp/tools/tools_integrations.py" = ["C901"]
|
|
199
|
+
"src/ha_mcp/tools/tools_system.py" = ["C901"]
|
|
200
|
+
"src/ha_mcp/tools/tools_traces.py" = ["C901"]
|
|
201
|
+
"src/ha_mcp/tools/tools_updates.py" = ["C901"]
|
|
202
|
+
"src/ha_mcp/tools/tools_yaml_config.py" = ["C901"]
|
|
203
|
+
"src/ha_mcp/tools/tools_zones.py" = ["C901"]
|
|
204
|
+
"src/ha_mcp/transforms/categorized_search.py" = ["C901"]
|
|
205
|
+
"src/ha_mcp/utils/data_paths.py" = ["C901"]
|
|
206
|
+
"src/ha_mcp/utils/fuzzy_search.py" = ["C901"]
|
|
207
|
+
"src/ha_mcp/utils/skill_loader.py" = ["C901"]
|
|
208
|
+
"src/ha_mcp/visibility/resolver.py" = ["C901"]
|
|
209
|
+
"tests/src/e2e/conftest.py" = ["C901"]
|
|
210
|
+
"tests/src/e2e/error_handling/test_network_errors.py" = ["C901"]
|
|
211
|
+
"tests/src/e2e/policy/test_readonly_mode.py" = ["C901"]
|
|
212
|
+
"tests/src/e2e/utilities/cleanup.py" = ["C901"]
|
|
213
|
+
"tests/src/e2e/utilities/wait_helpers.py" = ["C901"]
|
|
214
|
+
"tests/src/e2e/workflows/automation/test_helpers.py" = ["C901"]
|
|
215
|
+
"tests/src/e2e/workflows/automation/test_lifecycle.py" = ["C901"]
|
|
216
|
+
"tests/src/e2e/workflows/core/test_bulk.py" = ["C901"]
|
|
217
|
+
"tests/src/e2e/workflows/device_control/test_lights.py" = ["C901"]
|
|
218
|
+
"tests/src/e2e/workflows/scripts/test_lifecycle.py" = ["C901"]
|
|
219
|
+
"tests/src/haos_runtime.py" = ["C901"]
|
|
220
|
+
"tests/src/unit/_embedded_stubs.py" = ["C901"]
|
|
221
|
+
"tests/src/unit/test_action_discriminator.py" = ["C901"]
|
|
222
|
+
"tests/src/unit/test_advanced_settings_coverage.py" = ["C901"]
|
|
223
|
+
"tests/src/unit/test_fields_projection_docstring_completeness.py" = ["C901"]
|
|
224
|
+
"tests/src/unit/test_read_only.py" = ["C901"]
|
|
225
|
+
"tests/src/unit/test_settings_ui_js_behavior.py" = ["C901"]
|
|
226
|
+
"tests/test_env_manager.py" = ["C901"]
|
|
227
|
+
"tests/uat/ha_wait.py" = ["C901"]
|
|
228
|
+
"tests/uat/openai_agent.py" = ["C901"]
|
|
229
|
+
"tests/uat/run_uat.py" = ["C901"]
|
|
230
|
+
"tests/uat/stories/run_story.py" = ["C901"]
|
|
165
231
|
|
|
166
232
|
[tool.pytest.ini_options]
|
|
167
233
|
testpaths = ["tests"]
|
{ha_mcp_dev-7.12.0.dev1834 → ha_mcp_dev-7.12.0.dev1837}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
@@ -388,6 +388,11 @@ def _check_template_string(
|
|
|
388
388
|
prefix AND the suggestion text adapt if a future caller passes "trigger".
|
|
389
389
|
The native shapes named here (numeric_state, state, time, sun) work as
|
|
390
390
|
both conditions and triggers in HA — only the noun changes.
|
|
391
|
+
|
|
392
|
+
Detection is split across three helpers (comparison/sun/is_state, time
|
|
393
|
+
patterns, then state-list/direct-state/duration) purely to keep each
|
|
394
|
+
function's branching low. They run in the same order as before and
|
|
395
|
+
share ``duration_match`` so the split is invisible to callers.
|
|
391
396
|
"""
|
|
392
397
|
initial_count = len(warnings)
|
|
393
398
|
label = position.capitalize()
|
|
@@ -400,6 +405,44 @@ def _check_template_string(
|
|
|
400
405
|
# fires instead).
|
|
401
406
|
duration_match = _RE_DURATION_MATH.search(template)
|
|
402
407
|
|
|
408
|
+
_check_template_comparison_patterns(
|
|
409
|
+
template, warnings, skill_prefix, position, label, duration_match
|
|
410
|
+
)
|
|
411
|
+
_check_template_time_patterns(template, warnings, skill_prefix, position, label)
|
|
412
|
+
_check_template_state_and_duration_patterns(
|
|
413
|
+
template, warnings, skill_prefix, position, label, duration_match
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Generic fallback: any Jinja in this logic position that didn't match
|
|
417
|
+
# a specific detector. Catches new anti-patterns (issue #1011) and
|
|
418
|
+
# reframes #695 from "enumerate bad shapes" to "surface every template
|
|
419
|
+
# in a logic position". Specific detectors above keep their tailored
|
|
420
|
+
# messages.
|
|
421
|
+
if len(warnings) == initial_count and _RE_ANY_TEMPLATE.search(template):
|
|
422
|
+
_emit(
|
|
423
|
+
warnings,
|
|
424
|
+
f"Template detected in {position} — if this maps to a native option "
|
|
425
|
+
"(`numeric_state`, `state`, `time`, `sun`, `zone`, `device`), use that "
|
|
426
|
+
"instead. Templates fail silently at runtime and bypass schema validation.",
|
|
427
|
+
skill_prefix,
|
|
428
|
+
"template-guidelines.md#when-to-avoid-templates",
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _check_template_comparison_patterns(
|
|
433
|
+
template: str,
|
|
434
|
+
warnings: BestPracticeCheckResult,
|
|
435
|
+
skill_prefix: str | None,
|
|
436
|
+
position: str,
|
|
437
|
+
label: str,
|
|
438
|
+
duration_match: re.Match[str] | None,
|
|
439
|
+
) -> None:
|
|
440
|
+
"""Flag numeric-comparison, `sun.sun`, and `is_state()` template shapes.
|
|
441
|
+
|
|
442
|
+
Split out of :func:`_check_template_string` to keep its complexity low.
|
|
443
|
+
``duration_match`` is precomputed by the caller so the numeric-comparison
|
|
444
|
+
check can suppress its suggestion when duration math is also present.
|
|
445
|
+
"""
|
|
403
446
|
if _RE_NUMERIC_CMP.search(template) and not duration_match:
|
|
404
447
|
_emit(
|
|
405
448
|
warnings,
|
|
@@ -429,6 +472,19 @@ def _check_template_string(
|
|
|
429
472
|
skill_prefix,
|
|
430
473
|
"automation-patterns.md#native-conditions",
|
|
431
474
|
)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _check_template_time_patterns(
|
|
478
|
+
template: str,
|
|
479
|
+
warnings: BestPracticeCheckResult,
|
|
480
|
+
skill_prefix: str | None,
|
|
481
|
+
position: str,
|
|
482
|
+
label: str,
|
|
483
|
+
) -> None:
|
|
484
|
+
"""Flag `now().hour/minute`, weekday, and date-based template shapes.
|
|
485
|
+
|
|
486
|
+
Split out of :func:`_check_template_string` to keep its complexity low.
|
|
487
|
+
"""
|
|
432
488
|
if _RE_NOW_TIME.search(template):
|
|
433
489
|
_emit(
|
|
434
490
|
warnings,
|
|
@@ -458,6 +514,21 @@ def _check_template_string(
|
|
|
458
514
|
skill_prefix,
|
|
459
515
|
"automation-patterns.md#native-conditions",
|
|
460
516
|
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _check_template_state_and_duration_patterns(
|
|
520
|
+
template: str,
|
|
521
|
+
warnings: BestPracticeCheckResult,
|
|
522
|
+
skill_prefix: str | None,
|
|
523
|
+
position: str,
|
|
524
|
+
label: str,
|
|
525
|
+
duration_match: re.Match[str] | None,
|
|
526
|
+
) -> None:
|
|
527
|
+
"""Flag `states(...) in [...]`, direct-state-access, and duration-math shapes.
|
|
528
|
+
|
|
529
|
+
Split out of :func:`_check_template_string` to keep its complexity low.
|
|
530
|
+
``duration_match`` is precomputed by the caller.
|
|
531
|
+
"""
|
|
461
532
|
if _RE_STATE_IN.search(template):
|
|
462
533
|
_emit(
|
|
463
534
|
warnings,
|
|
@@ -489,21 +560,6 @@ def _check_template_string(
|
|
|
489
560
|
"automation-patterns.md#native-conditions",
|
|
490
561
|
)
|
|
491
562
|
|
|
492
|
-
# Generic fallback: any Jinja in this logic position that didn't match
|
|
493
|
-
# a specific detector. Catches new anti-patterns (issue #1011) and
|
|
494
|
-
# reframes #695 from "enumerate bad shapes" to "surface every template
|
|
495
|
-
# in a logic position". Specific detectors above keep their tailored
|
|
496
|
-
# messages.
|
|
497
|
-
if len(warnings) == initial_count and _RE_ANY_TEMPLATE.search(template):
|
|
498
|
-
_emit(
|
|
499
|
-
warnings,
|
|
500
|
-
f"Template detected in {position} — if this maps to a native option "
|
|
501
|
-
"(`numeric_state`, `state`, `time`, `sun`, `zone`, `device`), use that "
|
|
502
|
-
"instead. Templates fail silently at runtime and bypass schema validation.",
|
|
503
|
-
skill_prefix,
|
|
504
|
-
"template-guidelines.md#when-to-avoid-templates",
|
|
505
|
-
)
|
|
506
|
-
|
|
507
563
|
|
|
508
564
|
# ---------------------------------------------------------------------------
|
|
509
565
|
# Action tree checks
|
|
@@ -687,42 +743,21 @@ def _check_target_dict(
|
|
|
687
743
|
def _check_triggers(
|
|
688
744
|
triggers: Any, warnings: BestPracticeCheckResult, skill_prefix: str | None
|
|
689
745
|
) -> None:
|
|
690
|
-
"""Check triggers for device_id and template anti-patterns.
|
|
746
|
+
"""Check triggers for device_id and template anti-patterns.
|
|
747
|
+
|
|
748
|
+
Per-trigger detection is split into focused helpers (renamed-key check,
|
|
749
|
+
deprecated-behavior check, template-trigger check, numeric_state-trigger
|
|
750
|
+
check) purely to keep this function's complexity low. They run in the
|
|
751
|
+
same order as before.
|
|
752
|
+
"""
|
|
691
753
|
for trigger in _as_list(triggers):
|
|
692
754
|
if not isinstance(trigger, dict):
|
|
693
755
|
continue
|
|
694
756
|
|
|
695
757
|
platform = trigger.get("platform", trigger.get("trigger", ""))
|
|
696
758
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
_RENAMED_TRIGGER_KEYS.get(platform) if isinstance(platform, str) else None
|
|
700
|
-
)
|
|
701
|
-
if renamed_trigger:
|
|
702
|
-
_emit(
|
|
703
|
-
warnings,
|
|
704
|
-
f"Trigger key `{platform}` was renamed to `{renamed_trigger}` "
|
|
705
|
-
"in HA 2026.7 and the old key no longer loads — use "
|
|
706
|
-
f"`trigger: {renamed_trigger}`.",
|
|
707
|
-
skill_prefix,
|
|
708
|
-
"automation-patterns.md#trigger-types",
|
|
709
|
-
)
|
|
710
|
-
|
|
711
|
-
# 2026.7 renamed trigger `options.behavior` values (any→each, last→all).
|
|
712
|
-
options = trigger.get("options")
|
|
713
|
-
if isinstance(options, dict):
|
|
714
|
-
behavior = options.get("behavior")
|
|
715
|
-
if isinstance(behavior, str) and behavior in _DEPRECATED_TRIGGER_BEHAVIOR:
|
|
716
|
-
_emit(
|
|
717
|
-
warnings,
|
|
718
|
-
f"Trigger `options.behavior: {behavior}` was renamed to "
|
|
719
|
-
f"`{_DEPRECATED_TRIGGER_BEHAVIOR[behavior]}` in HA 2026.7 — "
|
|
720
|
-
"the old value still loads but raises a repair issue and "
|
|
721
|
-
"will be removed. Valid trigger values: `each`, `first`, "
|
|
722
|
-
"`all` (conditions keep `any`/`all`).",
|
|
723
|
-
skill_prefix,
|
|
724
|
-
"automation-patterns.md#trigger-types",
|
|
725
|
-
)
|
|
759
|
+
_check_renamed_trigger_key(platform, warnings, skill_prefix)
|
|
760
|
+
_check_deprecated_trigger_behavior(trigger, warnings, skill_prefix)
|
|
726
761
|
|
|
727
762
|
# Device trigger → prefer entity_id-based triggers
|
|
728
763
|
if platform == "device":
|
|
@@ -739,71 +774,131 @@ def _check_triggers(
|
|
|
739
774
|
|
|
740
775
|
# Template trigger — specific shapes first, generic fallback after.
|
|
741
776
|
if platform == "template":
|
|
742
|
-
|
|
743
|
-
if isinstance(vt, str):
|
|
744
|
-
initial = len(warnings)
|
|
745
|
-
# See `_check_template_string`: duration math also trips the numeric
|
|
746
|
-
# comparison detector, but maps to `for:`, not `numeric_state`. Suppress
|
|
747
|
-
# the numeric_state suggestion when duration math is present.
|
|
748
|
-
duration_match = _RE_DURATION_MATH.search(vt)
|
|
749
|
-
if _RE_NUMERIC_CMP.search(vt) and not duration_match:
|
|
750
|
-
_emit(
|
|
751
|
-
warnings,
|
|
752
|
-
"Trigger uses template with float/int comparison — "
|
|
753
|
-
"use native `numeric_state` trigger instead "
|
|
754
|
-
"(e.g., `platform: numeric_state, entity_id: sensor.temp, above: 30`).",
|
|
755
|
-
skill_prefix,
|
|
756
|
-
"automation-patterns.md#trigger-types",
|
|
757
|
-
)
|
|
758
|
-
if _RE_IS_STATE.search(vt):
|
|
759
|
-
_emit(
|
|
760
|
-
warnings,
|
|
761
|
-
"Trigger uses template with `is_state()` — use "
|
|
762
|
-
"native `state` trigger instead "
|
|
763
|
-
"(e.g., `platform: state, entity_id: light.x, to: 'on'`).",
|
|
764
|
-
skill_prefix,
|
|
765
|
-
"automation-patterns.md#trigger-types",
|
|
766
|
-
)
|
|
767
|
-
if duration_match:
|
|
768
|
-
_emit(
|
|
769
|
-
warnings,
|
|
770
|
-
"Trigger uses template for duration/recency check "
|
|
771
|
-
"(`now() - X.last_changed/last_updated`) — use the native "
|
|
772
|
-
"`for:` field on a `state` trigger instead "
|
|
773
|
-
"(e.g., `platform: state, entity_id: binary_sensor.motion, "
|
|
774
|
-
"to: 'off', for: {minutes: 5}`). Native `for:` is event-driven "
|
|
775
|
-
"and doesn't re-evaluate on every state change.",
|
|
776
|
-
skill_prefix,
|
|
777
|
-
"automation-patterns.md#trigger-types",
|
|
778
|
-
)
|
|
779
|
-
# Generic fallback for unmatched template triggers.
|
|
780
|
-
if len(warnings) == initial and _RE_ANY_TEMPLATE.search(vt):
|
|
781
|
-
_emit(
|
|
782
|
-
warnings,
|
|
783
|
-
"Trigger uses `template` platform — if this maps to a native option "
|
|
784
|
-
"(`state`, `numeric_state`, `time`, `time_pattern`, `sun`, `zone`, "
|
|
785
|
-
"`event`), use that instead. Native triggers are event-driven; "
|
|
786
|
-
"template triggers re-evaluate on every state change.",
|
|
787
|
-
skill_prefix,
|
|
788
|
-
"automation-patterns.md#trigger-types",
|
|
789
|
-
)
|
|
777
|
+
_check_template_trigger(trigger, warnings, skill_prefix)
|
|
790
778
|
|
|
791
779
|
# numeric_state trigger: value_template can also contain duration math
|
|
792
780
|
# (e.g. transforming last_changed into a seconds value for the threshold).
|
|
793
781
|
if platform == "numeric_state":
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
782
|
+
_check_numeric_state_trigger(trigger, warnings, skill_prefix)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _check_renamed_trigger_key(
|
|
786
|
+
platform: Any, warnings: BestPracticeCheckResult, skill_prefix: str | None
|
|
787
|
+
) -> None:
|
|
788
|
+
"""Flag HA 2026.7 purpose-specific trigger key renames (old keys no longer load)."""
|
|
789
|
+
renamed_trigger = (
|
|
790
|
+
_RENAMED_TRIGGER_KEYS.get(platform) if isinstance(platform, str) else None
|
|
791
|
+
)
|
|
792
|
+
if renamed_trigger:
|
|
793
|
+
_emit(
|
|
794
|
+
warnings,
|
|
795
|
+
f"Trigger key `{platform}` was renamed to `{renamed_trigger}` "
|
|
796
|
+
"in HA 2026.7 and the old key no longer loads — use "
|
|
797
|
+
f"`trigger: {renamed_trigger}`.",
|
|
798
|
+
skill_prefix,
|
|
799
|
+
"automation-patterns.md#trigger-types",
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def _check_deprecated_trigger_behavior(
|
|
804
|
+
trigger: dict[str, Any], warnings: BestPracticeCheckResult, skill_prefix: str | None
|
|
805
|
+
) -> None:
|
|
806
|
+
"""Flag HA 2026.7 trigger `options.behavior` value renames (any→each, last→all)."""
|
|
807
|
+
options = trigger.get("options")
|
|
808
|
+
if not isinstance(options, dict):
|
|
809
|
+
return
|
|
810
|
+
behavior = options.get("behavior")
|
|
811
|
+
if isinstance(behavior, str) and behavior in _DEPRECATED_TRIGGER_BEHAVIOR:
|
|
812
|
+
_emit(
|
|
813
|
+
warnings,
|
|
814
|
+
f"Trigger `options.behavior: {behavior}` was renamed to "
|
|
815
|
+
f"`{_DEPRECATED_TRIGGER_BEHAVIOR[behavior]}` in HA 2026.7 — "
|
|
816
|
+
"the old value still loads but raises a repair issue and "
|
|
817
|
+
"will be removed. Valid trigger values: `each`, `first`, "
|
|
818
|
+
"`all` (conditions keep `any`/`all`).",
|
|
819
|
+
skill_prefix,
|
|
820
|
+
"automation-patterns.md#trigger-types",
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _check_template_trigger(
|
|
825
|
+
trigger: dict[str, Any], warnings: BestPracticeCheckResult, skill_prefix: str | None
|
|
826
|
+
) -> None:
|
|
827
|
+
"""Check a `template` trigger's `value_template` for anti-patterns.
|
|
828
|
+
|
|
829
|
+
Split out of :func:`_check_triggers` to keep its complexity low.
|
|
830
|
+
"""
|
|
831
|
+
vt = trigger.get("value_template", "")
|
|
832
|
+
if not isinstance(vt, str):
|
|
833
|
+
return
|
|
834
|
+
initial = len(warnings)
|
|
835
|
+
# See `_check_template_string`: duration math also trips the numeric
|
|
836
|
+
# comparison detector, but maps to `for:`, not `numeric_state`. Suppress
|
|
837
|
+
# the numeric_state suggestion when duration math is present.
|
|
838
|
+
duration_match = _RE_DURATION_MATH.search(vt)
|
|
839
|
+
if _RE_NUMERIC_CMP.search(vt) and not duration_match:
|
|
840
|
+
_emit(
|
|
841
|
+
warnings,
|
|
842
|
+
"Trigger uses template with float/int comparison — "
|
|
843
|
+
"use native `numeric_state` trigger instead "
|
|
844
|
+
"(e.g., `platform: numeric_state, entity_id: sensor.temp, above: 30`).",
|
|
845
|
+
skill_prefix,
|
|
846
|
+
"automation-patterns.md#trigger-types",
|
|
847
|
+
)
|
|
848
|
+
if _RE_IS_STATE.search(vt):
|
|
849
|
+
_emit(
|
|
850
|
+
warnings,
|
|
851
|
+
"Trigger uses template with `is_state()` — use "
|
|
852
|
+
"native `state` trigger instead "
|
|
853
|
+
"(e.g., `platform: state, entity_id: light.x, to: 'on'`).",
|
|
854
|
+
skill_prefix,
|
|
855
|
+
"automation-patterns.md#trigger-types",
|
|
856
|
+
)
|
|
857
|
+
if duration_match:
|
|
858
|
+
_emit(
|
|
859
|
+
warnings,
|
|
860
|
+
"Trigger uses template for duration/recency check "
|
|
861
|
+
"(`now() - X.last_changed/last_updated`) — use the native "
|
|
862
|
+
"`for:` field on a `state` trigger instead "
|
|
863
|
+
"(e.g., `platform: state, entity_id: binary_sensor.motion, "
|
|
864
|
+
"to: 'off', for: {minutes: 5}`). Native `for:` is event-driven "
|
|
865
|
+
"and doesn't re-evaluate on every state change.",
|
|
866
|
+
skill_prefix,
|
|
867
|
+
"automation-patterns.md#trigger-types",
|
|
868
|
+
)
|
|
869
|
+
# Generic fallback for unmatched template triggers.
|
|
870
|
+
if len(warnings) == initial and _RE_ANY_TEMPLATE.search(vt):
|
|
871
|
+
_emit(
|
|
872
|
+
warnings,
|
|
873
|
+
"Trigger uses `template` platform — if this maps to a native option "
|
|
874
|
+
"(`state`, `numeric_state`, `time`, `time_pattern`, `sun`, `zone`, "
|
|
875
|
+
"`event`), use that instead. Native triggers are event-driven; "
|
|
876
|
+
"template triggers re-evaluate on every state change.",
|
|
877
|
+
skill_prefix,
|
|
878
|
+
"automation-patterns.md#trigger-types",
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def _check_numeric_state_trigger(
|
|
883
|
+
trigger: dict[str, Any], warnings: BestPracticeCheckResult, skill_prefix: str | None
|
|
884
|
+
) -> None:
|
|
885
|
+
"""Flag a `numeric_state` trigger's `value_template` used for duration math.
|
|
886
|
+
|
|
887
|
+
Split out of :func:`_check_triggers` to keep its complexity low.
|
|
888
|
+
"""
|
|
889
|
+
vt = trigger.get("value_template", "")
|
|
890
|
+
if isinstance(vt, str) and _RE_DURATION_MATH.search(vt):
|
|
891
|
+
_emit(
|
|
892
|
+
warnings,
|
|
893
|
+
"`numeric_state` trigger uses `value_template` for duration/recency "
|
|
894
|
+
"check (`now() - X.last_changed/last_updated`) — use the native "
|
|
895
|
+
"`for:` field on a `state` trigger instead "
|
|
896
|
+
"(e.g., `platform: state, entity_id: binary_sensor.motion, "
|
|
897
|
+
"to: 'off', for: {minutes: 5}`). Native `for:` is event-driven "
|
|
898
|
+
"and doesn't re-evaluate on every state change.",
|
|
899
|
+
skill_prefix,
|
|
900
|
+
"automation-patterns.md#trigger-types",
|
|
901
|
+
)
|
|
807
902
|
|
|
808
903
|
|
|
809
904
|
# ---------------------------------------------------------------------------
|